Automatically add decorator to all inherited methods
Question:
I want in class B to automatically add the decorator _preCheck to all methods that have been inherited from class A. In the example b.double(5) is correctly called with the wrapper. I want to avoid to manually re-declare (override) the inherited methods in B but instead, automatically decorate them, so that on the call to b.add(1,2) also _preCheck wrapper is called.
Side note:
- I need to have a reference to the Instance of B in the wrapper (in my example via self)
- I want to avoid editing the Base class A.
- If possible, I want to encapsulate the decoration mechanism and initialization of it in the derived class B
class A(object):
def __init__(self, name):
self.name = name
def add(self, a, b):
return a + b
class B(A):
def __init__(self, name, foo):
super().__init__(name)
self.foo = foo
def _preCheck(func):
@wraps(func)
def wrapper(self, *args, **kwargs) :
print("preProcess", self.name)
return func(self, *args, **kwargs)
return wrapper
@_preCheck
def double(self, i):
return i * 2
b = B('myInst', 'bar')
print(b.double(5))
print(b.add(1,2))
Based on How can I decorate all inherited methods in a subclass
I thought a possible solutions might be to ad the following snippet into B’s init method:
for attr_name in A.__dict__:
attr = getattr(self, attr_name)
if callable(attr):
setattr(self, attr_name, self._preCheck(attr))
However, I get the following error. I suspect the 2nd argument comes from the ‘self’. .
TypeError: _preCheck() takes 1 positional argument but 2 were given
There exist solutions to similar problems where they either initialize the subclasses from within the base class :
Add decorator to a method from inherited class?
Apply a python decorator to all inheriting classes
Answers:
Decorators need to be added the class itself not the instance:
from functools import wraps
class A(object):
def __init__(self, name):
self.name = name
def add(self, a, b):
return a + b
class B(A):
def __init__(self, name, foo):
super().__init__(name)
self.foo = foo
def _preCheck(func):
@wraps(func)
def wrapper(self, *args, **kwargs) :
print("preProcess", self.name)
return func(self, *args, **kwargs)
return wrapper
@_preCheck
def double(self, i):
return i * 2
for attr_name in A.__dict__:
if attr_name.startswith('__'): # skip magic methods
continue
print(f"Decorating: {attr_name}")
attr = getattr(A, attr_name)
if callable(attr):
setattr(A, attr_name, B._preCheck(attr))
b = B('myInst', 'bar')
print(b.double(5))
print(b.add(1,2))
Out:
Decorating: add
preProcess myInst
10
preProcess myInst
3
As an alternative method, you could use a metaclass
to override A
s methods in B
rather than modifying A
itself.
from functools import wraps
# move the decorator out of the class in order to use it in the metaclass
def _preCheck(func):
@wraps(func)
def wrapper(self, *args, **kwargs) :
print("preProcess", self.name)
return func(self, *args, **kwargs)
return wrapper
class MetaParentDecorator(type):
def __new__(cls, name, bases, dct):
# iterate through base methods and create
# a decorated copy for the subclass
for base in bases:
for k, v in base.__dict__.items():
if callable(v) and not k.startswith('__'):
print(f"Decorating: {k}")
dct[k] = _preCheck(v)
return type.__new__(cls, name, bases, dct)
class A(object):
def __init__(self, name):
self.name = name
def add(self, a, b):
return a + b
class B(A, metaclass=MetaParentDecorator):
def __init__(self, name, foo):
super().__init__(name)
self.foo = foo
@_preCheck
def double(self, i):
return i * 2
b = B('myInst', 'bar')
print(b.double(5))
print(b.add(1, 2))
Results in:
Decorating: add
preProcess myInst
10
preProcess myInst
3
If you’re unfamiliar with metaclass
es, the simplest explanation is that they’re responsible for creating your class
. Typically, this is done by type
, which is why you need to subclass type
.
This has a few benefits:
- It can be reused for other
class
es.
- It leaves the base
class
unchanged, whereas the other version will directly modify A
.
- Since
your class
is only ever created once, __new__
will only ever run
once.
It does have drawbacks though, such as it being a lesser used part of Python so users of your code may be unfamiliar with it.
I want in class B to automatically add the decorator _preCheck to all methods that have been inherited from class A. In the example b.double(5) is correctly called with the wrapper. I want to avoid to manually re-declare (override) the inherited methods in B but instead, automatically decorate them, so that on the call to b.add(1,2) also _preCheck wrapper is called.
Side note:
- I need to have a reference to the Instance of B in the wrapper (in my example via self)
- I want to avoid editing the Base class A.
- If possible, I want to encapsulate the decoration mechanism and initialization of it in the derived class B
class A(object):
def __init__(self, name):
self.name = name
def add(self, a, b):
return a + b
class B(A):
def __init__(self, name, foo):
super().__init__(name)
self.foo = foo
def _preCheck(func):
@wraps(func)
def wrapper(self, *args, **kwargs) :
print("preProcess", self.name)
return func(self, *args, **kwargs)
return wrapper
@_preCheck
def double(self, i):
return i * 2
b = B('myInst', 'bar')
print(b.double(5))
print(b.add(1,2))
Based on How can I decorate all inherited methods in a subclass
I thought a possible solutions might be to ad the following snippet into B’s init method:
for attr_name in A.__dict__:
attr = getattr(self, attr_name)
if callable(attr):
setattr(self, attr_name, self._preCheck(attr))
However, I get the following error. I suspect the 2nd argument comes from the ‘self’. .
TypeError: _preCheck() takes 1 positional argument but 2 were given
There exist solutions to similar problems where they either initialize the subclasses from within the base class :
Add decorator to a method from inherited class?
Apply a python decorator to all inheriting classes
Decorators need to be added the class itself not the instance:
from functools import wraps
class A(object):
def __init__(self, name):
self.name = name
def add(self, a, b):
return a + b
class B(A):
def __init__(self, name, foo):
super().__init__(name)
self.foo = foo
def _preCheck(func):
@wraps(func)
def wrapper(self, *args, **kwargs) :
print("preProcess", self.name)
return func(self, *args, **kwargs)
return wrapper
@_preCheck
def double(self, i):
return i * 2
for attr_name in A.__dict__:
if attr_name.startswith('__'): # skip magic methods
continue
print(f"Decorating: {attr_name}")
attr = getattr(A, attr_name)
if callable(attr):
setattr(A, attr_name, B._preCheck(attr))
b = B('myInst', 'bar')
print(b.double(5))
print(b.add(1,2))
Out:
Decorating: add
preProcess myInst
10
preProcess myInst
3
As an alternative method, you could use a metaclass
to override A
s methods in B
rather than modifying A
itself.
from functools import wraps
# move the decorator out of the class in order to use it in the metaclass
def _preCheck(func):
@wraps(func)
def wrapper(self, *args, **kwargs) :
print("preProcess", self.name)
return func(self, *args, **kwargs)
return wrapper
class MetaParentDecorator(type):
def __new__(cls, name, bases, dct):
# iterate through base methods and create
# a decorated copy for the subclass
for base in bases:
for k, v in base.__dict__.items():
if callable(v) and not k.startswith('__'):
print(f"Decorating: {k}")
dct[k] = _preCheck(v)
return type.__new__(cls, name, bases, dct)
class A(object):
def __init__(self, name):
self.name = name
def add(self, a, b):
return a + b
class B(A, metaclass=MetaParentDecorator):
def __init__(self, name, foo):
super().__init__(name)
self.foo = foo
@_preCheck
def double(self, i):
return i * 2
b = B('myInst', 'bar')
print(b.double(5))
print(b.add(1, 2))
Results in:
Decorating: add
preProcess myInst
10
preProcess myInst
3
If you’re unfamiliar with metaclass
es, the simplest explanation is that they’re responsible for creating your class
. Typically, this is done by type
, which is why you need to subclass type
.
This has a few benefits:
- It can be reused for other
class
es. - It leaves the base
class
unchanged, whereas the other version will directly modifyA
. - Since
yourclass
is only ever created once,__new__
will only ever run
once.
It does have drawbacks though, such as it being a lesser used part of Python so users of your code may be unfamiliar with it.