Siempre he configurado metaclases algo como esto:

class SomeMetaClass(type):
    def __new__(cls, name, bases, dict):
        #do stuff here

Pero acabo de encontrar una metaclase que se definió así:

class SomeMetaClass(type):
    def __init__(self, name, bases, dict):
        #do stuff here

¿Hay alguna razón para preferir uno sobre el otro?

Actualización : tenga en cuenta que le estoy preguntando sobre el uso de __new__ y __init__ en una metaclase. Ya entiendo la diferencia entre ellos en otra clase. Pero en una metaclase, no puedo usar __new__ para implementar el almacenamiento en caché porque __new__ solo se llama al crear la clase en una metaclase.

50
Jason Baker 3 dic. 2009 a las 18:00

5 respuestas

La mejor respuesta

Si desea alterar los atributos dict antes de crear la clase, o cambiar la tupla de bases, debe usar __new__. Cuando __init__ ve los argumentos, el objeto de clase ya existe. Además, debe usar __new__ si desea devolver algo que no sea una clase recién creada del tipo en cuestión.

Por otro lado, cuando se ejecuta __init__, la clase existe. Por lo tanto, puede hacer cosas como dar una referencia a la clase recién creada a uno de sus objetos miembros.

Editar : se modificó la redacción para que quede más claro que por "objeto", me refiero a clase-objeto.

62
Matt Anderson 3 dic. 2009 a las 15:47

Varias diferencias, de hecho.

Por un lado, el primer argumento en __new__ y __init__ no es el mismo, lo cual no es ayudado por todos los que simplemente usan, cls. Alguien señaló esto y es fundamental entender la diferencia:

  • __new__ obtiene la metaclase - MyType en mi ejemplo (recuerde que la clase de nivel de aplicación aún no se ha creado). Aquí es donde puede alterar bases (que puede causar errores de resolución MRO si no tiene cuidado).

  • __init__ obtiene la nueva clase de nivel de aplicación , Bar y Foo y, para ese momento, el espacio de nombres de esta clase ha sido poblado, ver {{X3 }} en el ejemplo a continuación.

Código de ejemplo:

class Mixin:
    pass

class MyType(type):


    def __new__(mcls, name, bases, attrs, **kwargs):
        print("  MyType.__new__.mcls:%s" % (mcls))

        if not Mixin in bases:
            #could cause MRO resolution issues, but if you want to alter the bases
            #do it here
            bases += (Mixin,)

        #The call to super.__new__ can also modify behavior:
        #                                   👇 classes Foo and Bar are instances of MyType
        return super(MyType, mcls).__new__(mcls, name, bases, attrs)

        #now we're back to the standard `type` 
        #doing this will neuter most of the metaclass behavior, __init__ wont
        #be called.                         👇
        #return super(MyType, mcls).__new__(type, name, bases, attrs)

    def __init__(cls, name, bases, attrs):
        print("  MyType.__init__.cls:%s." % (cls))

        #I can see attributes on Foo and Bar's namespaces
        print("    %s.cls_attrib:%s" % (cls.__name__, getattr(cls, "cls_attrib", None)))
        return super().__init__(name, bases, attrs)


print("\n Foo class creation:")
class Foo(metaclass=MyType):
    pass


print("\n bar class creation:")
class Bar(Foo):
    #MyType.__init__ will see this on Bar's namespace
    cls_attrib = "some class attribute"

Salida:

 Foo class creation:
  MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
  MyType.__init__.cls:<class '__main__.test.<locals>.Foo'>.
    Foo.cls_attrib:None

 Bar class creation:
  MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
  MyType.__init__.cls:<class '__main__.test.<locals>.Bar'>.
    Bar.cls_attrib:some class attribute
3
JL Peyret 3 feb. 2020 a las 21:24

Puede implementar el almacenamiento en caché. Person("Jack") siempre devuelve un nuevo objeto en el segundo ejemplo, mientras que puede buscar una instancia existente en el primer ejemplo con __new__ (o no devolver nada si lo desea).

1
Otto Allmendinger 3 dic. 2009 a las 15:05

Como se ha dicho, si tiene la intención de alterar algo como las clases base o los atributos, deberá hacerlo en __new__. Lo mismo es cierto para el name de la clase, pero parece haber una peculiaridad con él. Cuando cambia name, no se propaga a __init__, aunque, por ejemplo, attr sí.

Entonces tendrás:

class Meta(type):
    def __new__(cls, name, bases, attr):
        name = "A_class_named_" + name
        return type.__new__(cls, name, bases, attr)

    def __init__(cls, name, bases, attr):
        print "I am still called '" + name + "' in init"
        return super(Meta, cls).__init__(name, bases, attr)

class A(object):
    __metaclass__ = Meta

print "Now I'm", A.__name__

Huellas dactilares

I am still called 'A' in init
Now I'm A_class_named_A

Esto es importante saber si __init__ llama a una súper metaclase que hace algo de magia adicional. En ese caso, uno tiene que cambiar el nombre nuevamente antes de llamar a super.__init__.

2
Debilski 11 mar. 2011 a las 21:24

Puede ver la escritura completa en los documentos oficiales, pero básicamente, __new__ se llama antes se crea el nuevo objeto (con el fin de crearlo) y __init__ se llama después de que se crea el nuevo objeto ( con el fin de inicializarlo).

El uso de __new__ permite trucos como el almacenamiento en caché de objetos (siempre devuelve el mismo objeto para los mismos argumentos en lugar de crear otros nuevos) o produce objetos de una clase diferente a la solicitada (a veces se utiliza para devolver subclases más específicas de la clase solicitada) . En general, a menos que esté haciendo algo bastante extraño, __new__ es de utilidad limitada. Si no necesita invocar tales trucos, quédese con __init__.

6
Ben Blank 3 dic. 2009 a las 15:16