Как получить атрибуты базовых классов в Python? - PullRequest
0 голосов
/ 21 сентября 2018

Итак, я пишу интерфейсную среду, которая позволяет людям писать набор команд.

То, что я пытаюсь сделать здесь, это то, что для каждого подкласса 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'

Теперь сгенерированные описания будут полностью неверными.Я хочу, чтобы, когда метод был переопределен, его собранное описание также было переопределено.Поэтому описание всегда должно указывать на метод, к которому Python обычно обращался бы, если бы люди делали это обычными способами (например, B.a.description)

Таким образом, ожидаемый результат изменится на

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

Полагаю, я могу сделать специальный случай из слова descriptions и исправить первый description метод.

Но есть ли более элегантный и Pythonic способ достичь этого?Если честно, self.__class__.__mro__ выглядит довольно ужасно.

Ответы [ 2 ]

0 голосов
/ 21 сентября 2018

Я думаю, вы можете исправить свою первую реализацию 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 обычным методом.Этот способ поиска не вызовет никакой рекурсии, он просто получит связанный метод.

0 голосов
/ 21 сентября 2018

Когда вы перебираете 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 продолжать проверять себя бесконечно долго.

...