41

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?

3
  • 6
    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)? Commented Oct 12, 2013 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. Commented Oct 12, 2013 at 14:55
  • Have you tried this stackoverflow.com/questions/7196376/… Commented Oct 2, 2019 at 15:06

7 Answers 7

7

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 
Sign up to request clarification or add additional context in comments.

2 Comments

Sorry I realised that I made a significant mistake in my last comment so I will delete that and rewrite here: What is the point of defining the abstract method here when you've concreted the method anyway? Also, is this Python2?
This does not work.
4

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)

Comments

1

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.

Comments

1

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.

Comments

0

The idea is to have a shadow function that will be used and let the abstract function not have the decorator.

import abc class Foo(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod def my_method(self, x): pass @some_decorator def _my_method(self, x): self.my_method(x) class SubFoo(Foo): def my_method(self, x): print x 

In this case, use the shadow method _my_method in place instead of my_method when depending on this abstract class.

Comments

0

@Paul Becotte gets close. Here is a complete example based on his. Note his answer was missing the actual decorator, which needs special syntax to consume the 'self' parameter.

from abc import ABCMeta, abstractmethod # A base / metaclass class MyMeta(metaclass=ABCMeta): # Show that the subclass adopts __new__ from the parent def __new__(cls, *args, **data): obj = super().__new__(cls, *args, **data) obj.prop = "BOO" return obj # A decorator for subclass methods def decorate(self, func): def _wrapper_(*args, **kw): return "Wrapped: "+func(self, *args, **kw) return _wrapper_ # You can't @decorate wrap an @abstractmethod # but you can capture it's name lookup within the subclass # and emulate a decorator def __getattribute__(self, name): if name == "fun": func = getattr(type(self), "fun") return self.decorate(func) return object.__getattribute__(self, name) # An abstract method, to be later defined by a subclass @abstractmethod def fun(self, data): pass # A non-abstract method, common to all sub-classes def common(self, data): s = f"I want to live with common people...: {data}" return s # A concrete derived class class MyClass(MyMeta): # Concrete implementation def fun(self, data): s = f"Concrete can be fun: {data}" return s # Method only in MyClass def another(self, data): s = f"There are methods in this madness: {data}" return s 

Lets try it (first, what happens if we forget this is a metaclass)

try: bada = MyMeta() except TypeError as er: print(er) print("Test pass - this was expected") 

Now use it properly, (BTW I'm using Jupyter, so the following is in concecutive cells).

bada = MyClass() bada.fun("but take care when lifting") 
bada.common("I want to do what common people do") 
bada.prop 
bada.another("Welcome to the house of fun()...") 

Comments

-1

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 

2 Comments

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.
There's nothing wrong with using the default implementation of an abstract method.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.