2

Hello I am recently looking about the metaclass in Python.

I learned about we can attach a metaclass to a class in this way:

class Metaclass(type):
    ...

class MyClass(metaclass=Metaclass):
    ...
  • The first question

I wonder what's the principle of the keyword argument (metaclass=...) in the above example. There seems no description for that in the doc

I know that there is keyword argument for a function, but I've never seen the form occurred in class inheritance.

  • The second question

We know that we can create a class using type() function in this way

cls = type(class_name, (base_class,), {func_name: func_ref})

Considering the metaclass=... form, how can I pass metaclass when creating class using type() instead of class keyword?

Thanks stackoverflowers!

WW00WW
  • 417
  • 4
  • 15
  • Does [this](https://docs.python.org/3/reference/datamodel.html#metaclasses) answer the first question? – timgeb Feb 09 '22 at 15:40
  • 1
    The `metaclass` kwarg *is* mentioned in the [docs](https://docs.python.org/3/reference/datamodel.html#metaclasses) – DeepSpace Feb 09 '22 at 15:47
  • Per the same docs, setting a metaclass using `type(_, _, _)` does not *seem* to be possible – DeepSpace Feb 09 '22 at 15:56
  • @timgeb Thank you. Yes I've looked this, but it doesn't provide something about principle of keyword argument in class inheritance, I am still confused – WW00WW Feb 09 '22 at 16:18
  • 1
    The meta class is the thing you call, not an argument you pass to `type`. `type(_, _, _)` gets replaced with `Metaclass(_, _, _)`. – chepner Feb 09 '22 at 17:16
  • The treatment of a `class` statement is discussed in [Section 3.3.3](https://docs.python.org/3/reference/datamodel.html#customizing-class-creation) of the language documentation. – chepner Feb 09 '22 at 17:22

2 Answers2

2

In general, the keyword arguments used with a class statement are passed to __init_subclass__, to be used however that function sees fit. metaclass, however, is used by the code generator itself to determine what gets called in order to define the class.

The metaclass is not an argument to be passed to a call to type; it is the callable that gets called instead of type. Very roughly speaking,

class MyClass(metaclass=Metaclass):
    ...

is turned into

MyClass = Metaclass('MyClass', (), ...)

type is simply the default metaclass.

(The gory details of how a class statement is executed can be found in Section 3.3.3 of the language documentation.)


Note that a metaclass does not have to be a subclass of type. You can do truly horrific things with metaclasses, like

>>> class A(metaclass=lambda x, y, z: 3): pass
...
>>> type(A)
<class 'int'>
>>> A
3

It doesn't really matter what the metaclass returns, as long as it accepts 3 arguments.

Please don't write actual code like this, though. A class statement should produce something that at least resembles a type.


In some sense, class statements are less magical than other statements. def statements, for example, can't be customized to produce something other than instances of function, and it's far harder to create a function object by calling types.FunctionType explicitly.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • class MyClass(metaclass=Metaclass): ... is turned into MyClass = Metaclass('MyClass', (), ...) – WW00WW Feb 10 '22 at 01:52
  • Thanks! `class MyClass(metaclass=Metaclass):` is turned into `MyClass = Metaclass('MyClass', (), ...)` really helps. And can I ask another question? So there is only one form of that `keyword argument` in the class definition, right? If not, are there more keyword argument besides `metaclass`? Could you tell me the source code of the logic of the translation from `class MyClass(metaclass=Metaclass):` to `MyClass = Metaclass('MyClass', (), ...)` in the cpython repository? – WW00WW Feb 10 '22 at 01:57
  • `metaclass` is, I believe, the only keyword argument that the Python interpreter knows and cares about and looks for. Other keyword arguments are just passed on to other hooks (like `__init_subclass__`) when present. As for source code, you'd have to dig into the source of the implementation (e.g., CPython). – chepner Feb 10 '22 at 02:54
  • Horrific `A`, +1 – timgeb Feb 10 '22 at 10:27
  • I'll mention in passing the [PEP 560](https://www.python.org/dev/peps/pep-0560/) appears to provide a rationale for using non-types as meta classes. – chepner Feb 10 '22 at 12:51
0

Yes I've looked this, but it doesn't provide something about principle of keyword argument in class inheritance, I am still confused

metaclass=... is not about inheritance.

Everything in Python is an object. So when you define a class MyClass, not only is MyClass() an object but the class MyClass itself is an object.

MyClass() is an instance of MyClass, simple enough.

But if MyClass is also an object, what class is it an instance of?

If you do nothing, the object (!) MyClass is an instance of the class type.

If you do class MyClass(metaclass=Metaclass) then MyClass is an instance of Metaclass.

Consider

class Metaclass(type):
    pass

class MyClass1:
    pass

class MyClass2(metaclass=Metaclass):
    pass

Both MyClass1 and MyClass2 inherit from object and only object.

>>> MyClass1.__mro__
(<class '__main__.MyClass1'>, <class 'object'>)
>>> MyClass2.__mro__
(<class '__main__.MyClass2'>, <class 'object'>)

But their types differ:

>>> type(MyClass1)
<class 'type'>
>>> type(MyClass2)
<class '__main__.Metaclass'>

When would you need this? Usually, you don't. And when you do, you do the kind of metaprogramming where you already know the why and how.

chepner
  • 497,756
  • 71
  • 530
  • 681
timgeb
  • 76,762
  • 20
  • 123
  • 145