我创建了一个定义__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
当我运行它时,类A定义中的for_prepare
关键字参数再次出现在__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
可以看到,for_prepare
项从字典中删除,传递给__new__
的字典是传递给__prepare__
的同一对象,也是弹出for_prepare
项的同一对象,但在__new__
中它又出现了!为什么从字典中删除的关键字会被重新添加?
和传递给的字典new是传递给的对象prepare
不幸的是,这就是你错的地方。
Python只回收相同的对象id。
如果你在__prepare__
中创建一个新的字典,你会发现kwds
的id在__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
输出:
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:nemetrismpfmpf.testtest_so4.py", line 22, in <module>
class A(metaclass=M, for_prepare='xyz'):
File "d:nemetrismpfmpf.testtest_so4.py", line 19, in __new__
return super().__new__(metaclass, name, bases, ns, **kwds)
TypeError: A.__init_subclass__() takes no keyword arguments
这不是元类的效果,而是**kwargs
的效果。每当使用**kwargs
调用函数时,当前字典被解包,而不是传递给。当一个函数接收到**kwargs
时,创建一个新的字典。
实际上,当调用方/被调用方都使用**kwargs
时,则任何一方看到的字典都是副本。
比较单独使用**kwargs
的设置:
def first(**kwargs):
print(f"Popped 'some_arg': {kwargs.pop('some_arg')!r}")
def second(**kwargs):
print(f"Got {kwargs} in the end")
def head(**kwargs):
first(**kwargs)
second(**kwargs)
head(a=2, b=3, some_arg="Watch this!", c=4)
# Popped 'some_arg': 'Watch this!'
# Got {'a': 2, 'b': 3, 'some_arg': 'Watch this!', 'c': 4} in the end
同样,在创建class
时分别调用__prepare__
和__new__
。它们的**kwargs
是浅拷贝,添加或删除项目对另一个调用都是不可见的。
- 不发送
**kwds
到__new__
,在python 3.6之后它将无法捕获它们。
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 = {metaclass}n name = {name}n'
f' bases = {bases}n ns = {ns}n kwds = {kwds}n id_kwds = {id(kwds)}')
return super().__new__(metaclass, name, bases, ns)
class A(metaclass=M, for_prepare='xyz'):
pass
a = A()
结果:
in M.__new__:
metaclass = <class '__main__.M'>
name = A
bases = ()
ns = {'__module__': '__main__', '__qualname__': 'A'}
kwds = {'for_prepare': 'xyz'}
id_kwds = 2101285477256