platypus
platypus

Reputation: 1205

Python metaclass behavior (not calling __new__), is there an explanation?

In a file named exp.py (below), I am trying to understand metaclasses in Python. It seems like when the metaclass's __new__ method uses the 'type' constructor to construct a class, its __new__ method does not get called by a subclass of a class using it as the metaclass:

class A(type):
    def __new__(cls, name, bases, dct):
        print "cls is:   ", cls
        print "name is:  ", name
        print "bases is: ", bases
        print "dct is:   ", dct
        print
        return super(A, cls).__new__(cls, name, bases, dct)

class B(object):
    __metaclass__ = A

class C(B): pass

class D(type):
    def __new__(cls, name, bases, dct):
        print "cls is:   ", cls
        print "name is:  ", name
        print "bases is: ", bases
        print "dct is:   ", dct
        return type(name, bases, dct)

class E(object):
    __metaclass__ = D

class F(E): pass

In the terminal:

>>> from exp import *
cls is:    <class 'exp.A'>
name is:   B
bases is:  (<type 'object'>,)
dct is:    {'count': 0, '__module__': 'exp', '__metaclass__': <class 'exp.A'>, '__init__': <function __init__ at 0x107eb9578>}

cls is:    <class 'exp.A'>
name is:   C
bases is:  (<class 'exp.B'>,)
dct is:    {'__module__': 'exp'}

cls is:    <class 'exp.D'>
name is:   E
bases is:  (<type 'object'>,)
dct is:    {'count': 0, '__module__': 'exp', '__metaclass__': <class 'exp.D'>, '__init__': <function __init__ at 0x107ebdb18>}
>>> 

As you can see, the __new__ method of the metaclass D does not get called when class F's definition is loaded. (If it had been called, then information about the class F would have been printed too.)

Could someone help me explain this?


Related post: I was reading What is a metaclass in Python?, and seem to encounter something similar in the sentence, "Be careful here that the __metaclass__ attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a __metaclass__ attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior." But I did not fully understand this.

Upvotes: 3

Views: 1278

Answers (1)

Ashwini Chaudhary
Ashwini Chaudhary

Reputation: 250951

In the second case instead of returning an instance of the metaclass you're actually returning an instance of type, this in turn sets the __class__ attribute of the newly created class E as <type 'type'> instead of D. Hence as per the rule 2 Python checks for base class's __class__ attribute(or type(E) or E.__class__) and decides to use type as F's metaclass, so D's __new__ is never called in this case.

  • If dict['__metaclass__'] exists, it is used.
  • Otherwise, if there is at least one base class, its metaclass is used (this looks for a __class__ attribute first and if not found, uses its type).

So, the proper way is to call type's __new__ method in metaclass's __new__ method:

class D(type):
    def __new__(cls, name, bases, dct):
        print "cls is:   ", cls
        print "name is:  ", name
        print "bases is: ", bases
        print "dct is:   ", dct
        return type.__new__(cls, name, bases, dct)

class E(object):
    __metaclass__ = D

class F(E): pass

class A(object):
    pass

Output:

cls is:    <class '__main__.D'>
name is:   E
bases is:  (<type 'object'>,)
dct is:    {'__module__': '__main__', '__metaclass__': <class '__main__.D'>}
cls is:    <class '__main__.D'>
name is:   F
bases is:  (<class '__main__.E'>,)
dct is:    {'__module__': '__main__'}

Upvotes: 4

Related Questions