Replacing and decorating a class method in python - Sun, Mar 20, 2022
Replacing and decorating a class method in python
During development of the test project
for this blog I learnt how easy it is to replace or generally alter a method in python.
And since me and my colleagues where discussing the topic of observability
recently I wanted to try to decorate a method so that it’s parameters are logged before invocation and the result afterwards. So that the observability of the method would be increased without additional logging statements in the method itself which sometimes clutter up the code.
After doing a little bit of research here
and on stackoverlow
I programmed this little example which does the trick:
class Multiplier:
def multiply(self, a:int, b:int) -> int:
return a * b
if __name__ == '__main__':
def multiply_with_logging(func):
def impl(*args, **kwargs):
logging.debug(f"Call to {func.__qualname__} with arguments {args[1:]}")
ret_value = func(*args, **kwargs)
logging.debug(f"Call to {func.__qualname__} returned {ret_value}")
return ret_value
return impl
multiplier = Multiplier()
result = multiplier.multiply(3, 4)
logging.debug(f"Result is {result}")
Multiplier.multiply = multiply_with_logging(Multiplier.multiply)
result = multiplier.multiply(3, 4)
logging.debug(f"Result is {result}")
You can find the whole code here
. The class definition is pretty straight forward. The alteration occurs in the main method. At the top I create the wrapper which logs the arguments, invokes the wrapped method and returns the return value. And in the third last line I replace the instance method with the new method.
The output looks like this:
2022-03-20 15:10:10,754 - root - DEBUG - Result is 12
2022-03-20 15:10:10,754 - root - DEBUG - Call to Multiplier.multiply with arguments (3, 4)
2022-03-20 15:10:10,754 - root - DEBUG - Call to Multiplier.multiply returned 12
2022-03-20 15:10:10,754 - root - DEBUG - Result is 12
So the basic goal is achieved, though this prototype still needs some adoption. For example logging complex objects that do not implement def __str__(self):
(to string), logging kwargs
or logging the inner state of the instance before and after invocation.
In addition a more general decoration of arbitrary methods based on regular expressions that could be turned on and off would be nice. Like his example this PointCut example of AspectJ
:
@Pointcut("execution(public * *(..))")
Why will add an aspect to every public method.