I need to perform certain operations everytime the methods of a particular class is called (for example log the method name). How can this be achieved in Python in a generic way?
Decorate callable attributes from within a metaclass:
from functools import wraps
def _log_method(val):
@wraps(val)
def wrapper(*a, **ka):
print(val.__name__, 'is called')
val(*a, **ka)
return wrapper
class LogMethodCalls(type):
def __new__(cls, cls_name, bases, attrs):
for name, attr in attrs.items():
if callable(attr):
attrs[name] = _log_method(attr)
return type.__new__(cls, cls_name, bases, attrs)
class Foo(metaclass=LogMethodCalls):
def my_method(self):
pass
Foo().my_method() # my_method is called
Warning: This code only works for instance methods, methods that were decorated with @classmethod
or @staticmethod
will not be logged (because classmethod
and staticmethod
objects are not callable - they're just non-data descriptors).
The following works for class methods and static methods as well:
from functools import wraps
def _log_method(val):
@wraps(val)
def wrapper(*a, **ka):
print('calling', val.__name__)
val(*a, **ka)
return wrapper
class LogMethodCalls(type):
def __new__(cls, cls_name, bases, attrs):
for name, attr in attrs.items():
if callable(attr):
attrs[name] = _log_method(attr)
elif isinstance(attr, (classmethod, staticmethod)):
attrs[name] = type(attr)(_log_method(attr.__func__))
return type.__new__(cls, cls_name, bases, attrs)
class Foo(metaclass=LogMethodCalls):
def my_instance_method(self):
pass
@classmethod
def my_class_method(cls):
pass
@staticmethod
def my_static_method():
pass
Foo().my_instance_method() # calling my_instance_method
Foo.my_class_method() # calling my_class_method
Foo.my_static_method() # calling my_static_method
They have __func__
attributes that we can decorate.
Note that you'll need to use
class Foo(object):
__metaclass__ = LogMethodCalls
in Python 2.
Taken from this answer. You can use the inspect
module to look at the stack for the function name to create a simple logging function. Seems like kind of a hack, but I suppose it answers the question.
import inspect
def log_call():
print(inspect.stack()[1][3])
def my_func():
log_call()
# do stuff
my_func()
This will print my_func
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With