2011-08-06 5 views
21

metaclases Al hablar, the docs estatales:Usando el método __call__ de una metaclase en lugar de __new__?

Puede, por supuesto, también tienen en cuenta otros métodos de clase (o añadir nuevos métodos ); por ejemplo, la definición de un método personalizado __call__() en la metaclase permite el comportamiento personalizado cuando se llama a la clase, p. no creando siempre una nueva instancia.

Mi pregunta es: supongamos que quiero tener un comportamiento personalizado cuando se llama a la clase, por ejemplo el almacenamiento en caché en lugar de crear objetos nuevos. Puedo hacer esto anulando el método __new__ de la clase. ¿Cuándo querría definir una metaclase con __call__? ¿Qué ofrece este enfoque que no se puede lograr con __new__?

Respuesta

13

La respuesta directa a su pregunta es: cuando se quiere hacer más que sólo personalizar la creación de ejemplo, o cuando se quiere separar lo que la clase hace de cómo se creó.

Ver mi respuesta a Creating a singleton in Python y la discusión asociada.

Existen varias ventajas.

  1. Se le permite separar lo que la clase hace de los detalles de cómo se creó. La metaclase y la clase son responsables de una cosa.

  2. Puede escribir el código una vez en una metaclase y utilizarlo para personalizar el comportamiento de llamada de varias clases sin preocuparse por la herencia múltiple.

  3. subclases pueden anular el comportamiento en su método __new__, pero __call__ en una metaclase no tiene que llamar incluso __new__ en absoluto.

  4. Si la configuración funciona, puede hacerlo en el método __new__ de la metaclase, y solo ocurre una vez, en lugar de cada vez que se llama a la clase.

sin duda hay un montón de casos en los que la personalización de __new__ funciona igual de bien si no está preocupado por el principio de la responsabilidad individual.

Pero hay otros casos de uso que tienen que ocurrir antes, cuando se crea la clase, en lugar de cuando se crea la instancia. Es cuando estos entran a jugar que una metaclase es necesaria. Consulte What are your (concrete) use-cases for metaclasses in Python? para obtener muchos ejemplos excelentes.

0

Es una cuestión de fases del ciclo de vida y a qué tiene acceso. __call__ se llama después de__new__ y se pasa los parámetros de inicialización antes de pasan a __init__, por lo que puede manipularlos. Prueba este código y estudiar su salida:

class Meta(type): 
    def __new__(cls, name, bases, newattrs): 
     print "new: %r %r %r %r" % (cls, name, bases, newattrs,) 
     return super(Meta, cls).__new__(cls, name, bases, newattrs) 

    def __call__(self, *args, **kw): 
     print "call: %r %r %r" % (self, args, kw) 
     return super(Meta, self).__call__(*args, **kw) 

class Foo: 
    __metaclass__ = Meta 

    def __init__(self, *args, **kw): 
     print "init: %r %r %r" % (self, args, kw) 

f = Foo('bar') 
print "main: %r" % f 
+1

No! '__new__' en la metaclase ocurre cuando se crea _class_, no como _instance_.'__call__' sucede cuando' __new__' ocurriría sin la metaclase. – agf

+1

¿Dónde digo que '__new__' está relacionado con la creación de instancias? – pyroscope

+4

En realidad estaba preguntando sobre el '' __new__' de la clase, no sobre el '__new__' de la metaclase. –

12

Una diferencia es que mediante la definición de un método metaclase __call__ está exigiendo que se llama antes que cualquiera de la clase de o __new__ métodos de subclases tienen la oportunidad de ser llamado.

class MetaFoo(type): 
    def __call__(cls,*args,**kwargs): 
     print('MetaFoo: {c},{a},{k}'.format(c=cls,a=args,k=kwargs)) 

class Foo(object): 
    __metaclass__=MetaFoo 

class SubFoo(Foo): 
    def __new__(self,*args,**kwargs): 
     # This never gets called 
     print('Foo.__new__: {a},{k}'.format(a=args,k=kwargs)) 

sub=SubFoo() 
foo=Foo() 

# MetaFoo: <class '__main__.SubFoo'>,(),{} 
# MetaFoo: <class '__main__.Foo'>,(),{} 

Observe que SubFoo.__new__ nunca se llama. Por el contrario, si define Foo.__new__ sin una metaclase, permite que las subclases anulen Foo.__new__.

Por supuesto, puede definir MetaFoo.__call__ para llamar al cls.__new__, pero eso depende de usted. Al negarse a hacerlo, puede evitar que las subclases tengan su método __new__ llamado.

No veo una ventaja convincente para usar una metaclase aquí. Y dado que "Simple es mejor que complejo", recomendaría usar __new__.

+0

Note también que 'cls .__ new __()' será llamado indirectamente si el método 'MetaFoo .__ call __()' invoca 'super (MetaFoo, cls) .__ call __ (* args, ** kwargs)'. – martineau

5

Las diferencias sutiles se vuelven un poco más visibles cuando observa cuidadosamente el orden de ejecución de estos métodos.

class Meta_1(type): 
    def __call__(cls, *a, **kw): 
     print "entering Meta_1.__call__()" 
     rv = super(Meta_1, cls).__call__(*a, **kw) 
     print "exiting Meta_1.__call__()" 
     return rv 

class Class_1(object): 
    __metaclass__ = Meta_1 
    def __new__(cls, *a, **kw): 
     print "entering Class_1.__new__()" 
     rv = super(Class_1, cls).__new__(cls, *a, **kw) 
     print "exiting Class_1.__new__()" 
     return rv 

    def __init__(self, *a, **kw): 
     print "executing Class_1.__init__()" 
     super(Class_1,self).__init__(*a, **kw) 

Tenga en cuenta que el código anterior en realidad no lo hacen que no sea registramos lo que estamos haciendo nada. Cada método difiere a su implementación principal, es decir, su valor predeterminado. Así junto a la tala es más eficaz si simplemente había declarado cosas de la siguiente manera:

class Meta_1(type): pass 
class Class_1(object): 
    __metaclass__ = Meta_1 

Y ahora vamos a crear una instancia de Class_1

c = Class_1() 
# entering Meta_1.__call__() 
# entering Class_1.__new__() 
# exiting Class_1.__new__() 
# executing Class_1.__init__() 
# exiting Meta_1.__call__() 

Por lo tanto, si type es el padre de Meta_1 podemos imaginar un pseudo implementación de type.__call__() como tal:

class type: 
    def __call__(cls, *args, **kwarg): 

     # ... a few things could possibly be done to cls here... maybe... or maybe not... 

     # then we call cls.__new__() to get a new object 
     obj = cls.__new__(cls, *args, **kwargs) 

     # ... a few things done to obj here... maybe... or not... 

     # then we call obj.__init__() 
     obj.__init__(*args, **kwargs) 

     # ... maybe a few more things done to obj here 

     # then we return obj 
     return obj 

Comunicación de la orde llamada r arriba de Meta_1.__call__() (o en este caso type.__call__()) se le da la oportunidad de influir sobre si las llamadas a Class_1.__new__() y Class_1.__init__() finalmente se realizan o no. En el transcurso de su ejecución, Meta_1.__call__() podría devolver un objeto que ni siquiera ha sido tocado. Tomemos, por ejemplo, este enfoque para el patrón Singleton:

class Meta_2(type): 
    __Class_2_singleton__ = None 
    def __call__(cls, *a, **kw): 
     # if the singleton isn't present, create and register it 
     if not Meta_2.__Class_2_singleton__: 
      print "entering Meta_2.__call__()" 
      Meta_2.__Class_2_singleton__ = super(Meta_2, cls).__call__(*a, **kw) 
      print "exiting Meta_2.__call__()" 
     else: 
      print ("Class_2 singleton returning from Meta_2.__call__(), " 
        "super(Meta_2, cls).__call__() skipped") 
     # return singleton instance 
     return Meta_2.__Class_2_singleton__ 

class Class_2(object): 
    __metaclass__ = Meta_2 
    def __new__(cls, *a, **kw): 
     print "entering Class_2.__new__()" 
     rv = super(Class_2, cls).__new__(cls, *a, **kw) 
     print "exiting Class_2.__new__()" 
     return rv 

    def __init__(self, *a, **kw): 
     print "executing Class_2.__init__()" 
     super(Class_2, self).__init__(*a, **kw) 

Observemos lo que sucede cuando se intenta varias veces para crear un objeto de tipo Class_2

a = Class_2() 
# entering Meta_2.__call__() 
# entering Class_2.__new__() 
# exiting Class_2.__new__() 
# executing Class_2.__init__() 
# exiting Meta_2.__call__() 

b = Class_2() 
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped 

c = Class_2() 
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped 

print a is b is c 
True 

Ahora observar esta aplicación utilizando una clase __new__() método para tratar para lograr lo mismo.

import random 
class Class_3(object): 

    __Class_3_singleton__ = None 

    def __new__(cls, *a, **kw): 
     # if singleton not present create and save it 
     if not Class_3.__Class_3_singleton__: 
      print "entering Class_3.__new__()" 
      Class_3.__Class_3_singleton__ = rv = super(Class_3, cls).__new__(cls, *a, **kw) 
      rv.random1 = random.random() 
      rv.random2 = random.random() 
      print "exiting Class_3.__new__()" 
     else: 
      print ("Class_3 singleton returning from Class_3.__new__(), " 
        "super(Class_3, cls).__new__() skipped") 

     return Class_3.__Class_3_singleton__ 

    def __init__(self, *a, **kw): 
     print "executing Class_3.__init__()" 
     print "random1 is still {random1}".format(random1=self.random1) 
     # unfortunately if self.__init__() has some property altering actions 
     # they will affect our singleton each time we try to create an instance 
     self.random2 = random.random() 
     print "random2 is now {random2}".format(random2=self.random2) 
     super(Class_3, self).__init__(*a, **kw) 

en cuenta que la aplicación anterior a pesar de registrarse correctamente un producto único en la clase, no impide __init__() de ser llamado, esto sucede de manera implícita en type.__call__() (type siendo la metaclase defecto si no se especifica ninguno). Esto podría ocasionar algunos efectos no deseados:

a = Class_3() 
# entering Class_3.__new__() 
# exiting Class_3.__new__() 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.739298365475 

b = Class_3() 
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.247361634396 

c = Class_3() 
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.436144427555 

d = Class_3() 
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.167298405242 

print a is b is c is d 
# True 
Cuestiones relacionadas