【问题标题】:inherit meta class keyword arguments继承元类关键字参数
【发布时间】:2021-05-08 11:12:30
【问题描述】:

在 Python3.6+ 中,您可以在类的括号中提供元类关键字参数:MyClass(some_arg="foo") 但是这些 kwargs 似乎不会被任何子类继承:

class MyMeta(type):
    def __new__(cls, name, bases, local, **kwargs):
        print(f'{name} got kwargs: {kwargs}')
        return type.__new__(cls, name, bases, local)


class MyClass(metaclass=MyMeta, arg1="foo"):
    pass


class MySubClass(MyClass):
    pass

预期的输出是:

MyClass got kwargs: {'arg1': 'foo'}
MySubClass got kwargs: {'arg1': 'foo'}

但实际输出是:

MyClass got kwargs: {'arg1': 'foo'}
MySubClass got kwargs: {}

有没有办法在子类中继承这些 kwargs?

【问题讨论】:

  • 简短的回答是“不”。元类和元类参数并不是一个普遍有用的概念。它们对 Python 的内部有用,但对用户没有用。只需使用普通继承即可。

标签: python inheritance metaclass


【解决方案1】:

Python 不会自动将这些参数传递给已创建类的子类 - 正是由于以下原因,这可能更有用: 如果在创建子类时自动包含父类的参数,那么首先会出现的问题是:如何在最子类中合并所有超类的参数?

当然,显而易见的方法是遍历相反的方法解析顺序,并简单地将参数更新为字典,将最终值传递给最子类。

然而,这是 exact 使用普通旧继承的属性的行为 - 用户将无法进一步控制这些参数的传递方式。

通过使这些参数仅可用于实际构造的类,可以完全控制它们的使用方式,包括如何将它们传递给子类 - 如果您想让它们在子类中可用,只需创建它们作为父类的属性:

如果您希望语义与属性继承相同,则为每个关键字参数创建一个属性是“一种显而易见的方式”。您可能想为它们添加前缀,这样它们就不会与子类中的其他属性发生冲突:


prefix = "meta_"
class MyMeta(type):
    def __new__(cls, name, bases, local, **kwargs):
        # code to retrieve arguments from the bases:
        # (N.B. this does not replicate the MRO algorithm)
        for base in reversed(bases):
            for name_ in dir(base):
                if not name_.startswith(prefix):
                    continue
            kwargs[name_[len(prefix):]] = getattr(base, name)

        print(f'{name} got kwargs: {kwargs}')
        for key, value in kwargs.items():
             local[prefix + key] = value
        return type.__new__(cls, name, bases, local)


class MyClass(metaclass=MyMeta, arg1="foo"):
    pass


class MySubClass(MyClass):
    pass
 

(请记住,类中的__init_subclass__ 也可以获取这些参数,并且可能您甚至不需要元类,具体取决于您想要的用途)

而且,如果您不需要完整的继承行为,您可以在创建的类中使用具有固定名称的简单字典来记录父类中所有传递的参数。

而且 - 上面的“收集超类参数”代码只是需要,因为我保留了您作为示例的行为,以便在调用 type.__new__ 之前获取参数 - 否则您可以在创建类后获取属性,在最终类中,让 Python 处理继承逻辑:


prefix = "meta_"
class MyMeta(type):
    def __new__(cls, name, bases, local, **kwargs):
        for key, value in kwargs.items():
             local[prefix + key] = value
        cls = super().__new__(cls, name, bases, local)

        for name_ in dir(cls):
            if not name_.startswith(prefix):
                    continue
            print(f'{name} got custom parameter: {name_}: {getattr(cls, name_)}')
      
        return cls


class MyClass(metaclass=MyMeta, arg1="foo"):
    pass


class MySubClass(MyClass):
    pass
 

【讨论】:

    猜你喜欢
    • 2016-09-24
    • 2018-10-16
    • 2012-12-15
    • 2014-02-27
    • 2018-11-23
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 2022-07-28
    相关资源
    最近更新 更多