Why can't I change the __metaclass__ class attribute?

I have a strange and unusual use case for metaclasses where I would like to change the __metaclass__ base class after defining it, so that its subclasses will automatically use the new __metaclass__ . But this strangely doesn't work:

 class MetaBase(type): def __new__(cls, name, bases, attrs): attrs["y"] = attrs["x"] + 1 return type.__new__(cls, name, bases, attrs) class Foo(object): __metaclass__ = MetaBase x = 5 print (Foo.x, Foo.y) # prints (5, 6) as expected class MetaSub(MetaBase): def __new__(cls, name, bases, attrs): attrs["x"] = 11 return MetaBase.__new__(cls, name, bases, attrs) Foo.__metaclass__ = MetaSub class Bar(Foo): pass print(Bar.x, Bar.y) # prints (5, 6) instead of (11, 12) 

What I am doing can be very unreasonable / unsupported / undefined, but I canโ€™t understand for life how the old metaclass is called, and I would like to least understand how this is possible.

EDIT: Based on the assumption of jsbueno , I replaced the line Foo.__metaclass__ = MetaSub following line, which did exactly what I wanted:

 Foo = type.__new__(MetaSub, "Foo", Foo.__bases__, dict(Foo.__dict__)) 
+6
source share
3 answers

The metaclass information for the class is used at the time of its creation (either it is analyzed as a class block, or dynamically with an explicit call to the metaclass). It cannot be changed because the metaclass usually makes changes during class creation - the created class type is a metaclass. Its __metaclass__ attribute __metaclass__ not matter after its creation.

However, you can create a copy of this class and have a copy with a different metaclass than the original class.

In your example, if instead:

Foo.__metaclass__ = MetaSub

:

Foo = Metasub("Foo", Foo.__bases__, dict(Foo.__dict__))

You will achieve what you intended. The new Foo is for all effects equal to its predecessor, but with a different metaclass.

However, previously existing instances of Foo will not be considered an instance of the new Foo - if you need it, it is better to create a copy of Foo with a different name.

+2
source

The problem is that the __metaclass__ attribute __metaclass__ not used for inheritance, unlike what you expect. The "old" metaclass is not called either for Bar . The docs say the following about how the metaclass is discovered:

The corresponding metaclass is determined by the following rule priority:

  • If dict ['__ metaclass__'] exists, it is used.
  • Otherwise, if there is at least one base class, its metaclass is used (first it searches for the __class__ attribute, and if it is not found, it uses its type).
  • Otherwise, if there is a global variable named __metaclass__, it is used.
  • Otherwise, it uses a static style, a classic metaclass (types.ClassType).

So, what is actually used as a metaclass in your Bar class is in the parent attribute __class__ , and not in the parent attribute __metaclass__ .

More information can be found at fooobar.com/questions/213330 / ....

+3
source

Subclasses use __metaclass__ of their parent.

The solution for your use case is to program the parent __metaclass__ so that it has different behavior for the parent than for its subclasses. Perhaps he will check the class dictionary for the class variable and implement different types of behavior depending on its value (this type of method uses to control whether instances are set to dictionaries depending on whether the __slots__ parameter is defined).

+1
source

Source: https://habr.com/ru/post/906004/


All Articles