【问题标题】:Python - using metaclass & class attributes in object instancesPython - 在对象实例中使用元类和类属性
【发布时间】:2022-06-11 21:27:17
【问题描述】:

我有许多类继承了一个共同的类。我需要父类来跟踪在类级别定义的一堆依赖/关系。比如:

class Meta(type):
    ALLDEPENDENCIES = {}
    def __new__(meta, name, bases, attrs):
        if "DEPENDENCIES" in attrs.keys():
            for key, value in attrs.items():
                if key == "DEPENDENCIES":
                    meta.ALLDEPENDENCIES.update(attrs["DEPENDENCIES"])
        return type.__new__(meta, name, bases, attrs)

class DataTable(DataFrameWrapper, metaclass=Meta):
    pass

class Foo(DataTable):
    DEPENDENCIES = {"a":1}

class Bar(DataTable):
    DEPENDENCIES = {"b":2}

本质上,当我创建新类(Foo、Bar、Baz...)时,它们中的每一个都有一个字典。我需要合并每个字典中的信息。所以我使用的是元类,如上所示。每个类都作为一个 DEPENDENCIES 属性,我将所有这些都收集到元类中定义的 ALLDEPENDENCIES 属性中。

如果我这样做,它似乎可以正常工作:

import Foo, Bar
print(Foo.ALLDEPENDENCIES)
>> {"a":1, "b":2}
print(Bar.ALLDEPENDENCIES)
>> {"a":1, "b":2}

但是,当使用 obj 实例时,缺少 ALLDEPENDENCIES 属性:

f = Foo()
b = Bar()
print(f.ALLDEPENDENCIES)
print(b.ALLDEPENDENCIES)

属性错误 - 没有 ALLDEPENDENCIES。

我认为元类中定义的类属性可以从实例中的 self.myattribute 访问,就像 DEPENDENCIES 一样。我做错了什么?

【问题讨论】:

  • 这并不完全是一个骗局,但这里有一个很好的(虽然有点老)讨论可能会有所帮助:methods of metaclasses on class instances
  • "我认为元类中定义的类属性可以从实例中的 self.myattribute 访问" 不,实例在 类命名空间 中查找属性,而不是元类命名空间。注意,类是元类的实例,所以它们在它们的(元)类命名空间中查找属性
  • @juanpa.arrivillaga 啊,我明白了。但是我可以从实例访问该命名空间吗?理想情况下,不必在我可能需要查找的任何地方导入 Foo ?还是使用元类是一种完全错误的方法?虽然这对我来说似乎很合适。
  • FWIW,__init_subclass__ 方法可能更好地服务于这个用例。这可以让你避免编写元类。

标签: python metaclass class-attributes


【解决方案1】:

Meta 描述 how 创建类而不是 what 类。

Meta != Parent 具有继承属性

所以你必须将适当的属性传递给新类:

class Meta(type):
    _a = {}
    def __new__(meta, name, bases, attrs):
        if "d" in attrs:
            meta._a.update(attrs["d"])
        attrs["a"] = meta._a
        return type.__new__(meta, name, bases, attrs)

class Data:
    pass

class DataTable(Data, metaclass=Meta):
    pass

class Foo(DataTable):
    d = {"a":1}

class Bar(DataTable):
    d = {"b":2}

f = Foo()
print(Foo.a)
print(f.a)
{'a': 1, 'b': 2}
{'a': 1, 'b': 2}

【讨论】:

  • 阿姆夫。是的,这实际上很有意义。非常感谢。
  • 只是为我自己(以及其他可能阅读此内容的人)重新制定。它的要点是 meta.foo 是元类的属性。 attrs(new 中的最后一个参数)包含/将包含当前正在创建的类的属性。因此,需要在其中弹出 ALLDEPENDANCIES。
【解决方案2】:

实例类属性搜索不进入元类——只进入类。元类可以在每个新类中设置ALLDEPENDANCIES,在其__new__ 中设置一行,但是如果您想要更简洁的代码,从某种意义上说字典不是到处都有别名,您可以通过类访问属性。

按原样使用您的代码:

Foo().__class__.ALLDEPENDANCIES  

可以在任何地方工作(就像 `type(Foo()).ALLDEPENDANCIES)。

为了在新类中设置属性,使其在新创建的类中可见,一个选项是:


from types import MappingProxyType

class Meta(type):
    ALLDEPENDANCIES = {}
    ALLDEPSVIEW = MappingProxyType(ALLDEPENDANCIES)
    def __new__(meta, name, bases, attrs):
        if "DEPENDANCIES" in attrs.keys():
            for key, value in attrs.items():
                if key == "DEPENDANCIES":
                    meta.ALLDEPENDANCIES.update(attrs["DEPENDANCIES"])
        new_cls = super().__new__(meta, name, bases, attrs)
        new_cls.ALLDEPENDANCIES = meta.ALLDEPSVIEW
        return new_cls

(在调用 type.__new__ 之前在 attrs 中插入新的 attr 也可以)

在这里我做了另外两个额外的事情:(1) 调用 super().__new__ 而不是硬编码对 type.__new__ 的调用:这允许您的元类与其他元类组合,如果您的一个类将与其他元类组合,则可能需要这样做其他元类(例如,如果您使用来自abccollections.abc 的抽象基类)。并且 (2) 使用 MappingProxyType 这是一个“只读”字典视图,将停止通过类或实例对字典进行直接更新。

【讨论】:

    猜你喜欢
    • 2011-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-01
    • 1970-01-01
    相关资源
    最近更新 更多