Как вызвать переопределенный метод суперкласса из декорированного метода подкласса - PullRequest
1 голос
/ 04 августа 2020

Я пытаюсь создать декоратор, который расширяет функцию метода класса, когда метод переопределяется подклассом. Вот минимальный пример:

class Parent(object):
    def __init__(self):
        return

    def say_hello(self):
        print('Parent says hello')

class Child1(Parent):
    def __init__(self):
        super().__init__()

    def say_hello(self):
        print('Child says hello')

child = Child1()
child.say_hello() # this will print 'Child says hello'

Я бы хотел создать декоратор, который будет выполнять родительский метод в дополнение к дочернему.

def extendedmethod(method):
    def wrapper(obj):
        # call the method as defined by the superclass
        method(obj) # call the method defined by the subclass
    return wrapper

class Child2(Parent):
    def __init__(self):
        super().__init__()

    @extendedmethod
    def say_hello(self):
        print('Child says hello')

child = Child2()
child.say_hello() # I want this to print 'Parent says hello' then 'Child says hello'

Я думаю, что я действительно спрашивает, как мне получить доступ к суперклассу, из которого наследуется подкласс, внутри декоратора?

1 Ответ

3 голосов
/ 04 августа 2020

В явном коде мы бы написали следующее:

class Child2(Parent):
    def say_hello(self):
        super().say_hello()  # call baseclass method
        print('Child says hello')

Форма super() без аргументов компилируется в super(__class__, self) здесь, где __class__ = Child2. Важной частью является то, что super требует как экземпляр self, так и , владеющий классом .

Получить экземпляр просто - он передается декорированному методу при вызове. Задача состоит в том, чтобы извлечь класс, в котором находится декорированный метод.

Один из подходов - разработать наш декоратор как дескриптор (аналогично property) - дескрипторы могут определять метод __set_name__, чтобы получить свое имя и класс владельца . Кроме того, мы должны определить __get__, чтобы удовлетворить протоколу дескриптора и получить self, а также __call__ для фактического вызова методов:

class extendedmethod:
    """Decorator to call the superclass method before a method"""
    def __init__(self, method, owner=None):
        self.method = method
        self.owner = owner

    def __set_name__(self, owner, name):
        self.owner = owner

    def __get__(self, instance, owner=None):
        # self.__call__ with `__m_self__` and `__m_cls__` filled in
        return partial(self, instance, owner)

    def __call__(self, __m_self__, __m_cls__, *args, **kwargs):
        # self: the decorator instance
        # __m_self__: the `self` seen by a method
        # __m_cls__: the `cls` seen by a classmethod
        # super(__class__, self).say_hello ------------------------v 
        #      v super(__class__, self) ----v
        getattr(super(self.owner, __m_self__), self.method.__name__)(*args, **kwargs)
        return self.method.__get__(__m_self__, __m_cls__)(*args, **kwargs)

Этот декоратор может быть непосредственно применен к методу для вызова его метода суперкласса:

class Child2(Parent):
    @extendedmethod
    def say_hello(self):
        print('Child says hello')

Child2().say_hello()
# Parent says hello
# Child says hello
...