为什么类定义的关键字参数在删除后会重新出现?

sag*_*ian 6 python metaclass python-3.x

我创建了一个定义该__prepare__方法的元类,该方法应该使用类定义中的特定关键字,如下所示:

class M(type):
    @classmethod
    def __prepare__(metaclass, name, bases, **kwds):
        print('in M.__prepare__:')
        print(f'  {metaclass=}\n  {name=}\n'
              f'  {bases=}\n  {kwds=}\n  {id(kwds)=}')
        if 'for_prepare' not in kwds:
            return super().__prepare__(name, bases, **kwds)
        arg = kwds.pop('for_prepare')
        print(f'  arg popped for prepare: {arg}')
        print(f'  end of prepare: {kwds=} {id(kwds)=}')
        return super().__prepare__(name, bases, **kwds)

    def __new__(metaclass, name, bases, ns, **kwds):
        print('in M.__new__:')
        print(f'  {metaclass=}\n  {name=}\n'
              f'  {bases=}\n  {ns=}\n  {kwds=}\n  {id(kwds)=}')
        return super().__new__(metaclass, name, bases, ns, **kwds)


class A(metaclass=M, for_prepare='xyz'):
    pass
Run Code Online (Sandbox Code Playgroud)

当我运行它时,for_prepare类 A 定义中的关键字参数重新出现在__new__(以及稍后出现在 中__init_subclass__,它会导致错误):

$ python3 ./weird_prepare.py
in M.__prepare__:
  metaclass=<class '__main__.M'>
  name='A'
  bases=()
  kwds={'for_prepare': 'xyz'}
  id(kwds)=140128409916224
  arg popped for prepare: xyz
  end of prepare: kwds={} id(kwds)=140128409916224
in M.__new__:
  metaclass=<class '__main__.M'>
  name='A'
  bases=()
  ns={'__module__': '__main__', '__qualname__': 'A'}
  kwds={'for_prepare': 'xyz'}
  id(kwds)=140128409916224
Traceback (most recent call last):
  File "./weird_prepare.py", line 21, in <module>
    class A(metaclass=M, for_prepare='xyz'):
  File "./weird_prepare.py", line 18, in __new__
    return super().__new__(metaclass, name, bases, ns, **kwds)
TypeError: __init_subclass__() takes no keyword arguments
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,该for_prepare项目已从字典中删除,并且传递到的字典__new__是传递到的同一对象以及从中弹出__prepare__该项目的同一对象,但在其中重新出现了!为什么从字典中删除的关键字又被添加回来?for_prepare__new__

Mik*_*tty 6

传递给new 的dict 与传递给prepare的对象是同一个对象

不幸的是,这就是你错的地方。

Python只回收相同的对象id。

如果你在里面创建一个新的字典,__prepare__你会注意到 id 的kwds变化__new__

class M(type):
    @classmethod
    def __prepare__(metaclass, name, bases, **kwds):
        print('in M.__prepare__:')
        print(f'  {metaclass=}\n  {name=}\n'
              f'  {bases=}\n  {kwds=}\n  {id(kwds)=}')
        if 'for_prepare' not in kwds:
            return super().__prepare__(name, bases, **kwds)
        arg = kwds.pop('for_prepare')
        x = {} # <<< create a new dict
        print(f'  arg popped for prepare: {arg}')
        print(f'  end of prepare: {kwds=} {id(kwds)=}')
        return super().__prepare__(name, bases, **kwds)

    def __new__(metaclass, name, bases, ns, **kwds):
        print('in M.__new__:')
        print(f'  {metaclass=}\n  {name=}\n'
              f'  {bases=}\n  {ns=}\n  {kwds=}\n  {id(kwds)=}')
        return super().__new__(metaclass, name, bases, ns, **kwds)


class A(metaclass=M, for_prepare='xyz'):
    pass
Run Code Online (Sandbox Code Playgroud)

输出:

in M.__prepare__:
  metaclass=<class '__main__.M'>
  name='A'
  bases=()
  kwds={'for_prepare': 'xyz'}
  id(kwds)=2595838763072
  arg popped for prepare: xyz
  end of prepare: kwds={} id(kwds)=2595838763072
in M.__new__:
  metaclass=<class '__main__.M'>
  name='A'
  bases=()
  ns={'__module__': '__main__', '__qualname__': 'A'}
  kwds={'for_prepare': 'xyz'}
  id(kwds)=2595836298496 # <<< id has changed now
Traceback (most recent call last):
  File "d:\nemetris\mpf\mpf.test\test_so4.py", line 22, in <module>
    class A(metaclass=M, for_prepare='xyz'):
  File "d:\nemetris\mpf\mpf.test\test_so4.py", line 19, in __new__ 
    return super().__new__(metaclass, name, bases, ns, **kwds)     
TypeError: A.__init_subclass__() takes no keyword arguments        
Run Code Online (Sandbox Code Playgroud)