首页 > 解决方案 > 如何在 Python 中获取基类的属性?

问题描述

所以我正在编写一个允许人们编写命令集合的界面框架。

我在这里要做的是,对于 的每个子类Base,找到它的所有具有描述的方法,然后收集所有这些函数并从中生成一个集体描述(加上其他东西,但它与这个问题无关)

这里是 MCVE

import types
class Meta(type):

    def __init__(cls, *args, **kwargs):
        cls._interfaces = {}
        super().__init__(*args, **kwargs)

    def __call__(cls, id, *args, **kwargs):
        if id in cls._interfaces:
            return cls._interfaces[id]
        obj = cls.__new__(cls, *args, **kwargs)
        obj.__init__(*args, **kwargs)
        cls._interfaces[id] = obj
        return obj

class Base(metaclass=Meta):

    @property
    def descriptions(self): # this doesn't work. It gives RecursionError
        res=''
        for name in dir(self):
            attr=getattr(self,name)
            if isinstance(attr, types.FunctionType) and hasattr(attr, 'description'):
                res += f'{name}: {attr.description}\n'
        return res

    @property
    def descriptions(self):
        res=''
        for cls in self.__class__.__mro__:
            for name,attr in cls.__dict__.items():
                if isinstance(attr, types.FunctionType) and hasattr(attr, 'description'):
                    res += f'{name}: {attr.description}\n'
        return res

class A(Base):

    def a(self): pass
    a.description='A.a'

    def b(self): pass
    b.description='A.b'

class B(A):
    def c(self): pass
    c.description='B.c'

    def d(self): pass
    d.description='B.d'

print(B(0).descriptions)

现在我当前方法的问题是它没有检测到方法覆盖。假设我将此片段添加到类的定义中B

def a(self): pass
a.description='B.a'

现在生成的描述将完全错误。我想要的是,当一个方法被覆盖时,它收集的描述也被覆盖。因此,描述应该始终指向 Python 通常会解决的方法,如果人们以通常的方式(例如B.a.description

所以预期的输出将变为

a: B.a
b: A.b
c: B.c
d: B.d

我想我可以用单词制作一个特殊情况descriptions并修复第description一种方法。

但是有没有更优雅和 Pythonic 的方式来实现这一点?老实说,self.__class__.__mro__看起来很可怕。

标签: pythonoopinheritancepython-3.7

解决方案


当您迭代时self.__class__.__mro__,您正在迭代解决顺序,这将<class '__main__.B'> <class '__main__.A'> <class '__main__.Base'> <class 'object'>在您的示例中。

因此,您description随后会查看这些类中的每一个的属性。当您a.description = 'B.a'在类内部声明时B,您实际上并没有在父类A中覆盖它,您只是在子类中覆盖它B

所以,相反,你可以这样做:

import inspect


class Base(metaclass=Meta):
    def descriptions(self):
        res = ''
        for mtd in inspect.getmembers(self, predicate=inspect.ismethod):
            if hasattr(mtd[1], 'description'):
                res += f'{mtd[0]}: {mtd[1].description}\n'

        return res

这样,您检查实际的孩子而不是其父母。但是,Base.descriptions在这种情况下不能是属性,因为属性会在声明时进行评估,这会使inspect.getmembers检查自身的时间无限长。


推荐阅读