Question

Code is better than words here:

class MetaA(type):
    def __new__(cls, name, bases, attrs):
        print "MetaA"
        return super(MetaA, cls).__new__(cls, name, bases, attrs)


class A(object):
    __metaclass__ = MetaA

This will print MetaA

class MetaB(MetaA):
    def __new__(cls, name, bases, attrs):
        print "MetaB"
        return super(MetaB, cls).__new__(cls, name, bases, attrs)


B = type('B', (A, ), {'__metaclass__': MetaB})

This will print MetaA again (?!)

I would expect:

MetaB
MataA

The question are:

  1. why I'm getting MetaA only?
  2. How to change the code to get:

    MetaB
    MataA
    
Was it helpful?

Solution

The reason is that type(name, bases, dict) is not the right way to create class with specified metaclass.

Interpreter actually avoids calling type(name, bases, dict) when in sees __metaclass__ attribute defined and calls mateclass instead of type(name, bases, dict) as it would normally do.

Here is the relevant docs section that explains it:

When the class definition is read, if __ metaclass __ is defined then the callable assigned to it will be called instead of type(). This allows classes or functions to be written which monitor or alter the class creation process [...]

If you modify your code like this:

class MetaA(type):
    def __new__(cls, name, bases, attrs):
        print "MetaA"
        return super(MetaA, cls).__new__(cls, name, bases, attrs)

class A(object):
    __metaclass__ = MetaA


class MetaB(MetaA):
    def __new__(cls, name, bases, attrs):
        print "MetaB"
        return super(MetaB, cls).__new__(cls, name, bases, attrs)

class B(A):
    __metaclass__ = MetaB

... then you'll get the expected output:

MetaA
MetaB 
MetaA 

(first line printed when creating class A, second and third when creating class B)

UPDATE: The question assumed dynamic creation of the class B, so I'll extend my answer.

To construct the class B with metacalss dynamically you should do the same thing as interpreter does, i.e. construct class with metaclass in __metaclass__ instad of type(name, bases, dict).

Like this:

B = MetaB('B', (A, ), {'__metaclass__': MetaB})
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top