¿De costumbre heredar metaclases de tipo?

He estado tratando de entender las metaclases de Python, y así he estado revisando un código de ejemplo. Por lo que yo entiendo, una metaclase Python puede ser invocable. Entonces, puedo tener mi metaclase como

def metacls(clsName, bases, atts): .... return type(clsName, bases, atts) 

Sin embargo, he visto a muchas personas escribir sus metaclases de la siguiente manera:

 class Metacls(type): def __new__(meta, clsName, bases, atts): .... return type.__new__(meta, clsName, bases, atts) 

Por lo que puedo ver, ambos harían lo mismo. ¿Hay alguna razón para usar la clase base en su lugar? ¿Es costumbre?

Hay diferencias sutiles, en su mayoría relacionadas con la herencia. Cuando se utiliza una función como una metaclase, la clase resultante es realmente una instancia de type , y se puede heredar sin restricciones; sin embargo, la función de metaclase nunca será llamada para tales subclases. Cuando se usa una subclase de type como metaclase, la clase resultante será una instancia de esa metaclase, al igual que cualquiera de sus subclases; sin embargo, la herencia múltiple será restringida.

Ilustrando las diferencias:

 >>> def m1(name, bases, atts): >>> print "m1 called for " + name >>> return type(name, bases, atts) >>> >>> def m2(name, bases, atts): >>> print "m2 called for " + name >>> return type(name, bases, atts) >>> >>> class c1(object): >>> __metaclass__ = m1 m1 called for c1 >>> type(c1)  >>> class sub1(c1): >>> pass >>> type(sub1)  >>> class c2(object): >>> __metaclass__ = m2 m2 called for c2 >>> class sub2(c1, c2): >>> pass >>> type(sub2)  

Tenga en cuenta que al definir sub1 y sub2, no se llamaron funciones de metaclase. Se crearán exactamente como si c1 y c2 no tuvieran metaclases, sino que hayan sido manipulados después de la creación.

 >>> class M1(type): >>> def __new__(meta, name, bases, atts): >>> print "M1 called for " + name >>> return super(M1, meta).__new__(meta, name, bases, atts) >>> class C1(object): >>> __metaclass__ = M1 M1 called for C1 >>> type(C1)  >>> class Sub1(C1): >>> pass M1 called for Sub1 >>> type(Sub1)  

Tenga en cuenta las diferencias ya: se llamó a M1 al crear Sub1, y ambas clases son instancias de M1. Estoy usando super() para la creación real aquí, por razones que se aclararán más adelante.

 >>> class M2(type): >>> def __new__(meta, name, bases, atts): >>> print "M2 called for " + name >>> return super(M2, meta).__new__(meta, name, bases, atts) >>> class C2(object): >>> __metaclass__ = M2 M2 called for C2 >>> type(C2)  >>> class Sub2(C1, C2): >>> pass M1 called for Sub2 Traceback (most recent call last): File "", line 1, in  File "", line 23, in __new__ TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases 

Esta es la principal restricción en la herencia múltiple con metaclases. Python no sabe si M1 y M2 son metaclases compatibles, por lo que te obliga a crear uno nuevo para garantizar que hace lo que necesitas.

 >>> class M3(M1, M2): >>> def __new__(meta, name, bases, atts): >>> print "M3 called for " + name >>> return super(M3, meta).__new__(meta, name, bases, atts) >>> class C3(C1, C2): >>> __metaclass__ = M3 M3 called for C3 M1 called for C3 M2 called for C3 >>> type(C3)  

Esta es la razón por la que utilicé super() en las funciones de metaclase __new__ : para que cada uno pueda llamar al siguiente en el MRO.

Ciertos casos de uso pueden necesitar que sus clases sean del tipo de type , o tal vez quieran evitar los problemas de herencia, en cuyo caso una función de metaclase es probablemente el camino a seguir. En otros casos, el tipo de la clase puede ser verdaderamente importante, o tal vez quiera operar en todas las subclases, en cuyo caso el type subclase sería una mejor idea. Siéntase libre de usar el estilo que mejor se adapte a cualquier situación dada.