如何在 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
作为常规方法。这样查找它不会导致任何递归,它只会得到一个绑定方法。
所以我正在编写一个界面框架,允许人们编写一组命令。
我在这里要做的是,对于 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
作为常规方法。这样查找它不会导致任何递归,它只会得到一个绑定方法。