28

In python, is there a way to make a decorator on an abstract method carry through to the derived implementation(s)?

For example, in

import abc

class Foo(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    @some_decorator
    def my_method(self, x):
        pass

class SubFoo(Foo):
    def my_method(self, x):
        print x

SubFoo's my_method won't get decorated with some_decorator as far as I can tell. Is there some way I can make this happen without having to individually decorate each derived class of Foo?

Ian Hincks
  • 2,567
  • 21
  • 17
  • 5
    Only with extensive magic, I fear. Is it an alternative to call the abstract method `_my_method` and have a non-abstract, non-overridden `my_method` that basically does `some_decorator(self._my_method)(*args, **kwds)`? –  Oct 12 '13 at 14:50
  • @delnan Hmm, yeah, I was hoping there might be a solution somewhere between extensive metaclass magic and your good suggestion that's a bit cleaner. But I'll do what I have to do I guess. – Ian Hincks Oct 12 '13 at 14:55
  • Have you tried this https://stackoverflow.com/questions/7196376/python-abstractmethod-decorator – Tayyab Gulsher Vohra Oct 02 '19 at 15:06

5 Answers5

4

I would code it as two different methods just like in standard method factory pattern description.

https://www.oodesign.com/factory-method-pattern.html

class Foo(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    @some_decorator
    def my_method(self, x):
        self.child_method()

class SubFoo(Foo):
    def child_method(self, x):
        print x
Jinksy
  • 371
  • 2
  • 11
2

This is, of course, possible. There is very little that can't be done in Python haha! I'll leave whether it's a good idea up to you...

class MyClass:
    def myfunc():
        raise NotImplemented()

    def __getattribute__(self, name):
        if name == "myfunc":
            func = getattr(type(self), "myfunc")
            return mydecorator(func)
        return object.__getattribute__(self, name)

(Not tested for syntax yet, but should give you the idea)

Paul Becotte
  • 7,950
  • 2
  • 24
  • 39
0

My solution would be extending the superclass' method without overriding it.

import abc

class Foo(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    @some_decorator
    def my_method(self, x):
        pass

class SubFoo(Foo):
    def my_method(self, x):
        super().my_method(x)  #delegating the call to the superclass
        print x
pchai
  • 11
  • 2
  • 2
    This defeats the purpose of an abstract method. The entire idea of an abstract method is that they MUST be overridden by the subclass. Therefore, you do not call `super()` on it. In fact, I usually make my abstract methods raise a `NotImplementedError` in addition to being marked as abstract to make sure that `super()` is not used. – 1313e May 03 '19 at 01:38
  • There's nothing wrong with using the default implementation of an abstract method. – chepner Sep 17 '19 at 17:46
0

As far as I know, this is not possible and not a good strategy in Python. Here's more explanation.

According to the abc documentation:

When abstractmethod() is applied in combination with other method descriptors, it should be applied as the innermost decorator, as shown in the following usage examples: ...

In other words, we could write your class like this (Python 3 style):

from abc import ABCMeta, abstractmethod

class AbstractClass(metclass=ABCMeta):

    @property
    @abstactmethod
    def info(self):
        pass

But then what? If you derive from AbstractClass and try to override the info property without specifying the @property decorator, that would create a great deal of confusion. Remember that properties (and it's only an example) usually use the same name for their class method, for concision's sake:

class Concrete(AbstractMethod):

    @property
    def info(self):
        return

    @info.setter
    def info(self, new_info):
        new_info

In this context, if you didn't repeat the @property and @info.setter decorators, that would create confusion. In Python terms, that won't work either, properties being placed on the class itself, not on the instance. In other words, I guess it could be done, but in the end, it would create confusing code that's not nearly as easy to read as repeating a few decorator lines, in my opinion.

vincent-lg
  • 411
  • 3
  • 14
0

Jinksy's answer did not work for me, but with a small modification it did (I use different names but the idea should be clear):

def my_decorator(func):
    def wrapped(self, x, y):
        print('start')
        result = func(self, x, y)
        print('end')
        return result
    return wrapped
    


class A(ABC):
    @abstractmethod
    def f(self, x, y):
        pass
        
    @my_decorator
    def f_decorated(self, x, y):
        return self.f(x, y)
    

class B(A):
    def f(self, x, y):
        return x + y
    

B().f_decorated(1, 3)
[Out:]
start
end
4   

Notice that the important difference between this and what Jinksy wrote is that the abstract method is f, and when calling B().f_decorated it is the inherited, non-abstract method that gets called.

As I understand it, f_decorated can be properly defined because the abstractmethod decorator is not interfering with the decorator my_decorator.

Soap
  • 197
  • 11