使用 metaclasses 继承 class 字典

Inheriting the class dictionary with metaclasses

我正在使用 metaclass:

设置 class 属性 fields
class MyMeta(type):
    def __new__(mcs, name, bases, clsdict):

        clsdict['fields'] = {k: v
                             for k, v in clsdict.items()
                             if <my_condition>}
        return super(MyMeta, mcs).__new__(mcs, name, bases, clsdict)

class MyBaseClass(metaclass=MyMeta):
    fields = {}

以下实例化导致预期结果:

class SubClass(MyBaseClass):
    param1 = 1 # meets <my_condition>

>>> SubClass.fields
{param1: 1}

但是如果我现在subclass SubClass, fields是空的:

class SubSubClass(SubClass):
   pass

>>> SubSubClass.fields 
{}

我如何能够更新继承层次结构中所有 classes 的 classdict,以便 fields 变量将从基础 classes 更新?

您需要以某种方式保留超类的 fields,例如迭代 "bases" 并使用它们的 fields 作为起点:

class MyMeta(type):
    def __new__(mcs, name, bases, clsdict):
        if 'fields' not in clsdict:
            clsdict['fields'] = {}
        # Initialize "fields" from base classes
        for base in bases:
            try:
                clsdict['fields'].update(base.fields)
            except AttributeError:
                pass
        # Fill in new fields (I included a "trivial" condition here, just use yours instead.)
        clsdict['fields'].update({k: v for k, v in clsdict.items() if k.startswith('param')})
        return super(MyMeta, mcs).__new__(mcs, name, bases, clsdict)

它适用于 SubClassSubSubClass:

>>> SubClass.fields
{'param1': 1}

>>> SubSubClass.fields
{'param1': 1}

我建议将 fields 变成一个 属性 描述符,它从父 classes 获取 _fields 的所有内容。这样您还可以更轻松地自定义出现名称冲突等情况时发生的情况。

class MyMeta(type):
    def __new__(mcs, name, bases, clsdict):
        # change fields to _fields
        clsdict['_fields'] = {k: v
                             for k, v in clsdict.items()
                             if <my_condition>}
        return super(MyMeta, mcs).__new__(mcs, name, bases, clsdict)
    @property
    def fields(cls):
        # reversed makes most recent key value override parent values
        return {k:v 
                for c in reversed(cls.__mro__) 
                for k,v in getattr(c, '_fields', {}).items() }

用法:

class MyBaseClass(metaclass=MyMeta):
    fields = {}

class SubClass(MyBaseClass):
    param1 = 1

>>> SubClass.fields
{param1: 1}

class SubSubClass(SubClass):
   pass

>>> SubSubClass.fields 
{param1: 1} # success

现在,SomeChildClass.fields 的用法总是指元class 属性。 getattr 的第三个参数允许没有 _fields 属性的 classes(例如 object)静默失败。

使用描述符还具有防止子 class 意外覆盖 fields 属性的优点:

>>> SubSubClass.fields = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

如果需要,您还可以创建一个 setter,然后在 __init__ 方法中使用它(即返回使用 fields 而不是 _fields)因此 class 的其余部分与实现无关:

    @fields.setter
    def fields(cls, mapping):
        try:
            cls._fields.update(**mapping)
        except AttributeError:
            cls._fields = dict(**mapping)