Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can metaclass be any callable?

To catch your eyes:

I think the documentation might be wrong!

According to Python 2.7.12 documentation, 3.4.3. Customizing class creation:

__metaclass__ This variable can be any callable accepting arguments for name, bases, and dict. Upon class creation, the callable is used instead of the built-in type().

New in version 2.2.

However, this article argues:

Q: Wow! Can I use any type object as the __metaclass__?

A: No. It must be a subclass of the type of the base object. ...

So I did an experiment on my own:

class metacls(list):    # <--- subclassing list, rather than type
    def __new__(mcs, name, bases, dict):
        dict['foo'] = 'metacls was here'
        return type.__new__(mcs, name, bases, dict)

class cls(object):
    __metaclass__ = metacls
    pass

This gives me:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    class cls(object):
  File "test.py", line 4, in __new__
    return type.__new__(mcs, name, bases, dict)
TypeError: Error when calling the metaclass bases
    type.__new__(metacls): metacls is not a subtype of type

So is the document really wrong?

like image 620
nalzok Avatar asked Jul 28 '16 15:07

nalzok


1 Answers

No, any callable will do. In your case the type.__new__() method has a restriction you are violating; this has nothing to do with what you assign to __metaclass__.

A function is a callable:

def metaclass_function(name, bases, body):
    return type(name, bases, body)

This one just returns the result of type() (not type.__new__()), yet it is just a callable. The return value is used as the class. You could really return anything:

>>> class Foo(object):
...     __metaclass__ = lambda *args: []
...
>>> Foo
[]

Here the callable produced a list instance, so Foo is bound to a list. Not very useful, but __metaclass__ is just called to produce something, and that something is used directly.

In your example, the first argument to type.__new__() is not a type, and it is that call that fails. mcs is bound to list, and not a (subclass of) type. type.__new__() is free to set such restrictions.

Now, because a metaclass is still tied to the class object (type(ClassObj) returns it) and it is used when resolving attribute look-ups on the class object (where the attribute is not available in the class MRO), it is usually a honking good idea to make it a subclass of type, because that gives you the right implementation of things like __getattribute__. It is for that reason that type.__new__() make a restriction on what can be passed in as the first argument; it is that first argument that type() attaches to the class object returned.

like image 194
Martijn Pieters Avatar answered Sep 21 '22 13:09

Martijn Pieters