6

The Python docs say that the metaclass of a class can be any callable. All the examples I see use a class. Why not use a function? It's callable, and fairly simple to define. But it isn't working, and I don't understand why.

Here's my code:

class Foo(object):
    def __metaclass__(name, base, dict):
        print('inside __metaclass__(%r, ...)' % name)
        return type(name, base, dict)
print(Foo.__metaclass__)
class Bar(Foo):
    pass
print(Bar.__metaclass__)

Here's the output:

inside __metaclass__('Foo', ...)
<unbound method Foo.__metaclass__>
<unbound method Bar.__metaclass__>

The metaclass method is defined for both the parent and child classes. Why is it only getting called for the parent? (Yes, I tried using the classmethod and staticmethod decorators for my metaclass, neither works. Yes, this might seem to be a dup of Metaclass not being called in subclasses but they are a class, not a function, as a metaclass.)

Community
  • 1
  • 1
samwyse
  • 2,760
  • 1
  • 27
  • 38

1 Answers1

5

The answer is in the precedence rules for __metaclass__ lookup:

The appropriate metaclass is determined by the following precedence rules:

  • 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).
  • Otherwise, if a global variable named __metaclass__ exists, it is used.
  • Otherwise, the old-style, classic metaclass (types.ClassType) is used.

If we examine Foo.__class__ we find that it is <type 'type'>, which is expected as your metaclass function called type to construct Foo.

__class__ is set by type to the first parameter of type.__new__, which is why in class metaclasses we call type.__new__(cls, name, bases, dict) (or super(Metaclass, cls).__new__(cls, ...)). However, we can't do that if the metaclass is a function:

>>> def __metaclass__(name, base, dict):
>>>     print('inside __metaclass__(%r, %r, %r)' % (name, base, dict))
>>>     return type.__new__(__metaclass__, name, base, dict)
>>> class Foo(object):
>>>     __metaclass__ = __metaclass__
TypeError: Error when calling the metaclass bases
    type.__new__(X): X is not a type object (function)

Similarly, if we try to set Foo.__class__ to your __metaclass__ it fails, as the __class__ attribute must be a class:

>>> Foo.__class__ = Foo.__metaclass__.__func__
TypeError: __class__ must be set to new-style class, not 'function' object

So, the reason to make metaclasses classes inheriting type, as opposed to just callables, is to make them inheritable.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • I don't quite get it. Does the run-time _try_ to set `Foo.__class__` to the given `__metaclass__` at some point ? Or is it the "programmer's responsibility" ? – Sylvain Leroux Aug 20 '14 at 13:26
  • 1
    @SylvainLeroux that happens automatically *if* you call `type.__new__(cls, ...)` (or, equivalently, `super(MyMetaclass, cls).__new__(cls, ...)`). I'll clarify, thanks. – ecatmur Aug 20 '14 at 13:29