Search code examples
pythonpython-3.xabstract-classwrapperabc

Inherit wrapper from abstract method


I would like to systematically wrap some overriden method of a base class.

I am using ABC for the base class. I tried to wrap the @abstractmethod, putting the annotation before or after, but it doesn’t work. As I understand it, the the whole wrapped method is overriden.

from functools import wraps
from abc import ABC, abstractmethod

def print_before(func):
    @wraps(func)
    def out(*args, **kwargs):
        print('Hello')
        return func(*args, **kwargs)

    return out

class Base(ABC):
    @print_before
    @abstractmethod
    def test(self):
        pass

class Extend(Base):
    def test(self):
        print('World')

Here is what happens when we test:

Extend().test()

Result:

World

Desired:

Hello
World

I guess I’m not using the right method to get such behavior. What would be a nice pythonic way to run some code before and after an overriden method?


Solution

  • As you noticed, the decorator does not change overridden methods. You could decorate the method every time you create a subclass. You can even do it automatically with the magic method __init_subclass__.

    class Base(ABC):
        ...
    
        def __init_subclass__(cls):
            cls.test = print_before(cls.test)
    

    But i would not recommend this approach. It will probably destroy the ABC mechanisms and classes that inherit from Extend are decorated twice if they don't override the method.

    Here is a much easier approach. We define a concrete "public" method on Base that calls an abstract "private" method. In the child classes we then have to implement the "private" method.

    class Base(ABC):
        def test(self):
            # do something before
            result = self._do_work()
            # do something after
            return result
    
        @abstractmethod
        def _do_work(self):
            pass
    
    
    class Extend(Base):
        def _do_work(self):
            # your implementation here
    
    # use it like this:
    e = Extend()
    e.test()
    

    Another advantage is that you can change the behaviour of the "wrapper" in a child class which would be difficult with the decorator.