So I'm writing an interface framework that allow people to write a collection of commands.
What I am trying to do here is that, for each subclass of Base, find all of its methods that has a description, then collect all those functions and generate a collective description out of them (plus something else, but its not relevant to this question)
Here is 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)
Now the problem with my current method is that it doesn't detect method overrides. Suppose I add this snippet into the definition of class B
def a(self): pass
a.description='B.a'
Now the generated descriptions will be completely wrong. What I want is that when a method got overridden, it's collected description also got overridden. So the description should always point to the method that Python would normally resolve to if people do it with usual ways (e.g. B.a.description)
So the expected output would then change to
a: B.a
b: A.b
c: B.c
d: B.d
I suppose I can make a special case out of word descriptions and fix the first description method.
But is there a more elegant and Pythonic way to achieve that? To be honest, self.__class__.__mro__ looks pretty horrible.
When you iterate over self.__class__.__mro__, you are iterating over the resolution order, which would be <class '__main__.B'> <class '__main__.A'> <class '__main__.Base'> <class 'object'> in your example.
As a result, you are looking at the description properties of each of these classes subsequently. When you declare a.description = 'B.a' inside class B, you are not actually overriding it in the parent class A, you are just overriding it in the child class B.
So, instead, you could do something like this:
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
That way, you are only inspecting the actual child and not its parents. However, Base.descriptions cannot be a property in this case, because properties get evaluated upon declaration, which would make inspect.getmembers keep inspecting itself infinitely long.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With