如何在 Python 中获取基础 类' 属性?

How to get base classes' attributes in Python?

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

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

这里是 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)

现在我当前方法的问题是它不检测方法覆盖。假设我将这段代码添加到 class 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

我想我可以用 word descriptions 做一个特例并修复第一个 description 方法。

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

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

因此,您随后会查看每个 class 的 description 属性。当你在 class B 中声明 a.description = 'B.a' 时,你实际上并没有在 parent class A 中覆盖它,你只是在child class 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

那样的话,您 只是 检查实际的 child 而不是它的 parent。但是,在这种情况下,Base.descriptions 不能是 属性,因为属性在声明时进行评估,这会使 inspect.getmembers 无限长地检查自身。

我认为您可以修复 descriptions 的第一个实现,这可能比搞乱 MRO 更容易。您得到 RecursionError 的原因是 "descriptions" 将出现在属性名称列表中。当您对其执行 getattr(self, name) 时,它递归地调用自身,因为它是 属性。您可以通过仅查找不是 "descriptions" 的名称来轻松避免这种情况。您还有一个问题,当您在实例上按名称查找方法时,您将获得绑定方法对象,而不是函数。也许您应该在 type(self) 上调用 getattr 而不是在 self 上?那看起来像这样:

@property
def descriptions(self):
    res=''
    for name in dir(self):
        if name != "descriptions":                    # avoid recursion!
            attr = getattr(type(self), name, None)    # lookup name on class object
            if isinstance(attr, types.FunctionType) and hasattr(attr, 'description'):
                res += f'{name}: {attr.description}\n'
    return res

另一种避免该问题的方法是摆脱 property 装饰器,并保留 descriptions 作为常规方法。这样查找它不会导致任何递归,它只会得到一个绑定方法。