【问题标题】:Inheriting the class dictionary with metaclasses使用元类继承类字典
【发布时间】:2017-09-26 19:43:26
【问题描述】:

我正在使用元类设置类属性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}

但是如果我现在继承SubClassfields 是空的:

class SubSubClass(SubClass):
   pass

>>> SubSubClass.fields 
{}

如何更新继承层次结构中所有类的 classdict,以便从基类更新 fields 变量?

【问题讨论】:

    标签: python python-3.x inheritance metaclass


    【解决方案1】:

    我建议将fields 转换为属性描述符,该描述符从父类中获取_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 的用法总是指元类属性。 getattr 的第三个参数允许没有_fields 属性的类(例如object)静默失败。

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

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

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

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

    【讨论】:

    • 这比接受的答案更好,因为它提供了更新属性的实时视图。但是您可以直接在元类中使用cls.fields 属性中的collections.ChainMap 设置。这样,您就不需要在 fields 属性中的每个属性访问上创建字典视图,
    【解决方案2】:

    您需要以某种方式保留超类的fields,例如通过迭代“基础”并使用它们的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}
    

    【讨论】:

    • 谢谢,就是这样。唯一的问题:为什么clsdict['fields'].update(base.fields 上的try/except?如果缺少基类,您已经在基类上设置了fields
    • @ProfHase85:因为一个子类可以有多个基类,其中一些可能没有fields 属性。
    猜你喜欢
    • 2020-03-19
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 2016-09-21
    • 2021-05-01
    • 2012-01-28
    • 2021-05-08
    • 1970-01-01
    相关资源
    最近更新 更多