Creé una metaclase que define el método __prepare__ , que se supone que consume una palabra clave específica en la definición de la clase, así:
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 Cuando lo ejecuto, el argumento de la palabra clave for_prepare en la definición de la clase A vuelve a aparecer en __new__ (y luego en __init_subclass__ , donde provoca un error):
$ 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 Como puede ver, el elemento for_prepare se elimina del dict, y el dict que se pasa a __new__ es el mismo objeto que se pasó a __prepare__ y el mismo objeto del que se extrajo el elemento for_prepare , ¡pero en __new__ volvió a aparecer! ¿Por qué se vuelve a agregar una palabra clave que se eliminó del diccionario?
y el dict que se pasa a nuevo es el mismo objeto que se pasó a preparar
Desafortunadamente, aquí es donde te equivocas.
Python solo recicla la misma identificación de objeto.
Si crea un nuevo dict dentro __prepare__ , notará que la identificación de kwds cambia en __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'): passProducción:
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**kwds a __new__ , no los detectará después de 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 = 2101285477256Este no es un efecto de metaclases, sino de **kwargs . Cada vez que se llama a una función con **kwargs , el dict actual se descomprime y no se transmite . Cada vez que una función recibe **kwargs , se crea un nuevo dict .
En efecto, cuando tanto la persona que llama como la persona que recibe la llamada usan **kwargs , el dict visto por cualquiera es una copia .
Compare la configuración de usar **kwargs de forma aislada:
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 Del mismo modo, __prepare__ y __new__ se llaman por separado al crear una class . Sus **kwargs son copias superficiales y ni la adición ni la eliminación de elementos son visibles para la otra llamada.