在元类中传递父属性

Pass parent attribute in metaclass

metaclass定义如下:

class MyMeta(type):
    def __new__(metacls, name, bases, namespace, **kwargs):
        # uses attribute a,b,c,d
        a = namespace["a"]
        b = namespace["b"]
        c = namespace["c"]
        d = namespace["d"]
        ...
        return super().__new__(metacls, name, bases, namespace, **kwargs)

此元class 使用属性 abcd。 现在 classes 可以用这个 metaclass 创建为:

class A(metaclass=MyMeta):
    a = "etc/dev"
    b = 2
    c = "c"
    d = "d"

class B(metaclass=MyMeta):
    a = "etc/dev/null"
    b = 2
    c = "c"
    d = "d"

每个 class 的属性 a 是可变的。 我想提取基础 class 中的常量属性以避免重复:

class BaseClass:
    b = 2
    c = "c"
    d = "d"

并使用 metaclass 创建 classes:

class A(BaseClass, metaclass=MyMeta):
    a = "/etc/dev"

class B(BaseClass, metaclass=MyMeta):
    a = "/etc/dev/null"

元class 抱怨它无法从父 class.

中找到属性 b
test_mode = namespace["b"]
KeyError: 'b'

有没有办法将属性从父元传递给子元class?

限制条件: 无法更新 MyMeta metaclass,因为它已经 borrowed/imported它来自现有的图书馆。

您可以通过使元class __new__() 方法 检查所有基 classes 的命名空间class 正在定义中。这是一种方法。请注意,它会过滤掉名称空间中具有 dunder 标识符的属性。

由于您无法更改元数据 class 您“借用”了一个自定义元数据。我之前回答的这个修订版还使用 collection.ChainMap class 来考虑所有名称空间,而无需实际组合它们。

from collections import ChainMap
import re

class BorrowedMeta(type):
    def __new__(metacls, name, bases, namespace, **kwargs):
        # uses attribute a,b,c,d
        a = namespace["a"]
        b = namespace["b"]
        c = namespace["c"]
        d = namespace["d"]
        ...
        return super().__new__(metacls, name, bases, namespace, **kwargs)


def is_dunder(name):
    """Determine if name is a Python dunder identifier."""
    return re.match(r'^__[^\d\W]\w*\Z__$', name, re.UNICODE)


class MyMeta(BorrowedMeta):
    def __new__(metacls, name, bases, namespace, **kwargs):
        chainmap = ChainMap(*[namespace] +
                             [{k: v for (k, v) in vars(base).items() if not(is_dunder(k))}
                                                    for base in bases])

        return super().__new__(metacls, name, bases, {**chainmap}, **kwargs)


class BaseClass:
    b = 2
    c = "c"
    d = "d"

class A(BaseClass, metaclass=MyMeta):
    a = "/etc/dev"

class B(BaseClass, metaclass=MyMeta):
    a = "/etc/dev/null"


from pprint import pprint
pprint(vars(A))
print()
pprint(vars(B))

输出:

mappingproxy({'__doc__': None,
              '__module__': '__main__',
              'a': '/etc/dev',
              'b': 2,
              'c': 'c',
              'd': 'd'})

mappingproxy({'__doc__': None,
              '__module__': '__main__',
              'a': '/etc/dev/null',
              'b': 2,
              'c': 'c',
              'd': 'd'})

不,如果不改变 metaclass 的行为,您将无法为所欲为。

问题是 MyMeta.__new__ 正在查找的 namespace 参数 只包含 class 语句中定义的值新 class。它不包含任何继承的东西,因为 class 和它的基础还没有合并(这就是 MyMeta.__new__ 在其实现底部调用 super().__new__ 时所做的,之后它已经用a/b/c变量完成了它的事情。