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)?

¿Fue útil?

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 de cls).

     

Las implementaciones típicas crear una nueva instancia de la clase invocando   método __new__() de la superclase usando super(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 de cls, entonces el método __init__() de la nueva instancia será invocado como __init__(self[, ...]), donde self es la nueva instancia y el resto de argumentos son los mismos que   se pasaron a __new__().

     

Si __new__() no devuelve una instancia de cls, entonces no se invoca el método __init__() de la nueva instancia.

     

__new__() está destinado principalmente para permitir subclases de inmutable tipos (como int, str o tuple) 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top