为什么我不能更改类的 __metaclass__ 属性?

发布于 2024-12-26 16:33:45 字数 943 浏览 1 评论 0原文

我有一个奇怪且不寻常的元类用例,我想在定义基类后更改它的 __metaclass__ ,以便它的子类将自动使用新的 __metaclass__ >。但这奇怪的是不起作用:

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)

我所做的很可能是不明智/不受支持/未定义的,但我一生都无法弄清楚旧元类是如何被调用的,而且我至少想明白这是怎么可能的。

编辑:根据jsbueno提出的建议,我用以下行替换了Foo.__metaclass__ = MetaSub行,这正是我所要做的通缉:

Foo = type.__new__(MetaSub, "Foo", Foo.__bases__, dict(Foo.__dict__))

I have a weird and unusual use case for metaclasses where I'd like to change the __metaclass__ of a base class after it's been defined so that its subclasses will automatically use the new __metaclass__. But that oddly 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'm doing may very well be unwise/unsupported/undefined, but I can't for the life of me figure out how the old metaclass is being invoked, and I'd like to least understand how that's possible.

EDIT: Based on a suggestion made by jsbueno, I replaced the line Foo.__metaclass__ = MetaSub with the following line, which did exactly what I wanted:

Foo = type.__new__(MetaSub, "Foo", Foo.__bases__, dict(Foo.__dict__))

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

长伴 2025-01-02 16:33:45

问题是继承时不使用 __metaclass__ 属性,这与您的预期相反。 Bar 也不会调用“旧”元类。文档对于如何找到元类有以下说明:

适当的元类由以下优先级决定
规则:

  • 如果 dict['__metaclass__'] 存在,则使用它。
  • 否则,如果至少有一个基类,则使用其元类(这首先查找 __class__ 属性,如果没有找到,
    使用其类型)。
  • 否则,如果存在名为 __metaclass__ 的全局变量,则使用它。
  • 否则,将使用旧式经典元类 (types.ClassType)。

因此,在 Bar 类中实际用作元类的内容是在父级的 __class__ 属性中找到的,而不是在父级的 __metaclass__ 属性中找到的。

如需了解更多信息,请参阅此 StackOverflow 答案

The problem is the __metaclass__ attribute is not used when inherited, contrary to what you might expect. The 'old' metaclass isn't called either for Bar. The docs say the following about how the metaclass is found:

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.

So what is actually used as metaclass in your Bar class is found in the parent's __class__ attribute and not in the parent's __metaclass__ attribute.

More information can be found on this StackOverflow answer.

岛歌少女 2025-01-02 16:33:45

类的元类信息在创建时使用(要么解析为类块,要么动态地通过显式调用元类)。它无法更改,因为元类通常在类创建时进行更改 - 创建的类类型是元类。它的 __metaclass__ 属性一旦创建就不再相关。

但是,可以创建给定类的副本,并让该副本具有与原始类不同的元类。

在您的示例中,如果不执行:

Foo.__metaclass__ = MetaSub

而是执行:

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

你将会达到你的目的。新的 Foo 的所有效果都与其前身相同,但具有不同的元类。

但是,之前存在的 Foo 实例不会被视为新 Foo 的实例 - 如果您需要,最好创建 Foo而是使用不同的名称进行复制。

The metaclass information for a class is used at the moment it is created (either parsed as a class block, or dynamically, with a explicit call to the metaclass). It can't be changed because the metaclass usually does make changes at class creation time - the created class type is the metaclasse. Its __metaclass__ attribute is irrelevant once it is created.

However, it is possible to create a copy of a given class, and have the copy bear a different metclass than the original class.

On your example, if instead of doing:

Foo.__metaclass__ = MetaSub

you do:

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

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

However, previously existing instances of Foo won't be considered an instance of the new Foo - if you need that, you better create the Foo copy with a different name instead.

你另情深 2025-01-02 16:33:45

子类使用其父类的__metaclass__

您的用例的解决方案是对父类的 __metaclass__ 进行编程,以便父类与其子类具有不同的行为。也许让它检查类字典中的类变量并根据其值实现不同的行为
(这是 type 用于控制是否为实例提供字典的技术,具体取决于是否定义了 __slots__ )。

Subclasses use the __metaclass__ of their parent.

The solution to your use-case is to program the parent's __metaclass__ so that it will have different behaviors for the parent than for its subclasses. Perhaps have it inspect the class dictionary for a class variable and implement different behaviors depending on its value
(this is the technique type uses to control whether or not instances are given a dictionary depending on the whether or not __slots__ is defined).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文