¿Cuál es la diferencia entre el tipo y el tipo .__ new__ en Python?
-
25-09-2019 - |
Pregunta
Yo estaba escribiendo una metaclase y accidentalmente lo hizo así:
class MetaCls(type):
def __new__(cls, name, bases, dict):
return type(name, bases, dict)
... en lugar de la siguiente manera:
class MetaCls(type):
def __new__(cls, name, bases, dict):
return type.__new__(cls, name, bases, dict)
¿Cuál es exactamente la diferencia entre estos dos metaclases? Y más específicamente, lo que causó la primera de ellas a no funcionar correctamente (algunas clases no se pusieran en la metaclase)?
Solución
En el primer ejemplo que está creando toda una nueva clase:
>>> class MetaA(type):
... def __new__(cls, name, bases, dct):
... print 'MetaA.__new__'
... return type(name, bases, dct)
... def __init__(cls, name, bases, dct):
... print 'MetaA.__init__'
...
>>> class A(object):
... __metaclass__ = MetaA
...
MetaA.__new__
>>>
mientras que en el segundo caso que está llamando __new__
de los padres:
>>> class MetaA(type):
... def __new__(cls, name, bases, dct):
... print 'MetaA.__new__'
... return type.__new__(cls, name, bases, dct)
... def __init__(cls, name, bases, dct):
... print 'MetaA.__init__'
...
>>> class A(object):
... __metaclass__ = MetaA
...
MetaA.__new__
MetaA.__init__
>>>
Otros consejos
Lo primero que hay que averiguar es cómo funciona object.__new__()
.
Aquí es desde el documentación :
object.__new__(cls[, ...])
Llamado para crear una nueva instancia de la clase
cls
.__new__()
es un estático Método (especial con carcasa por lo que no es necesario declarar como tal) que se lleva la clase de los que se ha solicitado una instancia como primer argumento. Los argumentos restantes son las que pasan al constructor de objetos expresión (la llamada a la clase). El valor de retorno de__new__()
debe ser la nueva instancia de objeto (por lo general una instancia decls
).Las implementaciones típicas crear una nueva instancia de la clase invocando método
__new__()
de la superclase usandosuper(currentclass, cls).__new__(cls[, ...])
con argumentos apropiados y luego modificando la recién creada instancia según sea necesario antes de devolverlo.Si
__new__()
devuelve una instancia decls
, entonces el método__init__()
de la nueva instancia será invocado como__init__(self[, ...])
, dondeself
es la nueva instancia y el resto de argumentos son los mismos que se pasaron a__new__()
.Si
__new__()
no devuelve una instancia decls
, entonces no se invoca el método__init__()
de la nueva instancia.
__new__()
está destinado principalmente para permitir subclases de inmutable tipos (comoint
,str
otuple
) para personalizar la creación de la instancia. Tambien es comúnmente se reemplaza en metaclases personalizados con el fin de personalizar la clase la creación.
Así que en mg de. respuesta , el antiguo no llama a la función __init__
mientras que el segundo llamadas a funciones __init__
después llamando __new__
.
return type(name, bases, dict)
Lo que se obtiene de esto es un nuevo type
, y no una instancia MetaCls
en absoluto. En consecuencia, los métodos definidos en MetaCls
(incluyendo __init__
) no puede nunca ser llamado.
type.__new__
se llamará como parte de la creación de ese nuevo tipo, sí, pero el valor de cls
entrar en esa función va a ser type
y no MetaCls
.
Por favor remítase a la anotación a continuación, espero que esto ayuda.
class MetaCls(type):
def __new__(cls, name, bases, dict):
# return a new type named "name",this type has nothing
# to do with MetaCls,and MetaCl.__init__ won't be invoked
return type(name, bases, dict)
class MetaCls(type):
def __new__(cls, name, bases, dict):
# return a new type named "name",the returned type
# is an instance of cls,and cls here is "MetaCls", so
# the next step can invoke MetaCls.__init__
return type.__new__(cls, name, bases, dict)
class MyMeta(type):
def __new__(meta, cls, bases, attributes):
print 'MyMeta.__new__'
return type.__new__(meta, cls, bases, attributes)
def __init__(clsobj, cls, bases, attributes):
print 'MyMeta.__init__'
class MyClass(object):
__metaclass__ = MyMeta
foo = 'bar'
Otra forma de conseguir el mismo resultado:
cls = "MyClass"
bases = ()
attributes = {'foo': 'bar'}
MyClass = MyMeta(cls, bases, attributes)
MyMeta
es un invocable por lo Python utilizará el método __call__
especial.
Python buscará __call__
en el tipo de la MyMeta
(que es type
en nuestro caso)
"En las clases de nuevo estilo, invocaciones implícitas de métodos especiales se sólo se garantiza que funcione correctamente si se define en el tipo de un objeto, no en el diccionario de la instancia del objeto "
MyClass = MyMeta(...)
se interpreta como:
my_meta_type = type(MyMeta)
MyClass = my_meta_type.__call__(MyMeta, cls, bases, attributes)
Dentro de la type.__call__()
imagino algo como esto:
MyClass = MyMeta.__new__(MyMeta, cls, bases, attributes)
meta_class = MyClass.__metaclass__
meta_class.__init__(MyClass, cls, bases, attributes)
return MyClass
MyMeta.__new__()
decidirá cómo se construye el MyClass
:
type.__new__(meta, cls, bases, attributes)
se establece la metaclase correcta (que es MyMeta
) para MyClass
type(cls, bases, attributes)
fijará la metaclase predeterminado (que es de tipo) para MyClass
Es todo bastante bien descrito aquí .
Si usted no devuelve el tipo de objeto, no tiene sentido a la definición de una metaclase personalizado.