【问题标题】:How to get base classes' attributes in Python?如何在 Python 中获取基类的属性?
【发布时间】:2018-09-21 04:12:15
【问题描述】:

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

我在这里尝试做的是,对于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)

现在我当前方法的问题是它没有检测到方法覆盖。假设我将这个sn-p添加到类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__ 看起来很可怕。

【问题讨论】:

  • 希望使用覆盖发生什么?
  • 如果我猜对了,那么您应该使用“这不起作用”的版本,但检查type(self) 而不是self。但是,如果您不告诉我们您想要什么,就很难确定。
  • (或者更好,将属性放在 metaclass 并通过类而不是实例访问它。)
  • @user2357112 我想要的是,当一个方法被覆盖时,它收集的描述也被覆盖。因此,描述应该始终指向 Python 通常会解决的方法,如果人们以通常的方式(例如B.a.description)进行处理。

标签: python oop inheritance python-3.7


【解决方案1】:

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

因此,您随后会查看每个类的description 属性。当您在类B 中声明a.description = 'B.a' 时,实际上并没有在父类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 无限长地继续检查自己。

【讨论】:

    【解决方案2】:

    我认为您可以修复 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 保留为正常方法。这样查找它不会导致任何递归,它只会得到一个绑定的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-13
      • 1970-01-01
      • 2021-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多