Reputation: 4118
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
?
Upvotes: 40
Views: 12061
Reputation: 411
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.
Upvotes: 0
Reputation: 761
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
.
Upvotes: 1
Reputation: 451
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
Upvotes: 7
Reputation: 9997
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)
Upvotes: 4
Reputation: 559
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.
Upvotes: 1
Reputation: 9
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
Upvotes: -1