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

时间:2018-09-21 04:12:16

标签: python oop inheritance python-3.7

因此,我正在编写一个接口框架,使人们可以编写命令集合。

我在这里想要做的是,对于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'

现在生成的描述将完全错误。我想要的是,当某个方法被覆盖时,它所收集的描述也被覆盖。因此,说明应始终指向人们通常采用的方法(例如B.a.description)Python通常会解析的方法

因此预期输出将变为

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

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

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

3 个答案:

答案 0 :(得分:2)

当您遍历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保持检查自身无限长的时间。

答案 1 :(得分:1)

我认为您可以解决descriptions的第一个实现,这可能比搞MRO容易。得到RecursionError的原因是"descriptions"将显示在属性名称列表中。当您对它执行getattr(self, name)时,由于它是一个属性,它会递归调用自身。您可以通过仅查找名称而不是"descriptions"来避免这种情况。还存在一个问题,当您在实例上按名称查找方法时,将获得绑定的方法对象,而不是函数。也许您应该在getattr而不是type(self)上致电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保留为常规方法。这样查找它不会导致任何递归,只会得到一个绑定方法。

答案 2 :(得分:-1)

我们不太清楚您希望发生什么行为,但是inspect模块几乎可以确定您需要什么。例如

inspect.getmro(type(self))
inspect.getclasstree(inspect.getmro(type(self)))