I want to log every method call in some classes. I could have done
class Class1(object):
@log
def method1(self, *args):
...
@log
def method2(self, *args):
...
But I have a lot of methods in every class, and I don't want to decorate every one separately. Currently, I tried using a hack with metaclasses (overriding my logged class' __getattribute__
so that if I try to get a method, it'll return a logging method instead):
class LoggedMeta(type):
def __new__(cls, name, bases, attrs):
def __getattribute__(self, name_):
attr = super().__getattribute__(name_)
if isinstance(attr, (types.MethodType, types.FunctionType)) and not name_.startswith("__"):
return makeLogged(attr) #This returns a method that first logs the method call, and then calls the original method.
return attr
attrs["__getattribute__"] = __getattribute__
return type.__new__(cls, name, bases, attrs)
class Class1(object):
__metaclass__ = LoggedMeta
def method1(self, *args):
...
However, I'm on Python 2.X, and the super() syntax doesn't work. At the time I call super, I don't have the __getattribute__
's class (but I do have its class name), so I can't use the old super syntax super(Class, Inst)
.
I tried earlier to use metaclasses, but override all the methods instead of __getattribute__
, but I want to log static method calls also, and they gave me some trouble.
I searched for this type of question, but found no-one who tried changing a class this way.
Any ideas or help would be very appreciated.
EDIT: My solution was this (mostly taken from this thread):
import inspect, types
CLASS = 0
NORMAL = 1
STATIC = 2
class DecoratedMethod(object):
def __init__(self, func, type_):
self.func = func
self.type = type_
def __get__(self, obj, cls=None):
def wrapper(*args, **kwargs):
print "before"
if self.type == CLASS:
#classmethods (unlike normal methods) reach this stage as bound methods, but args still contains the class
#as a first argument, so we omit it.
ret = self.func(*(args[1:]), **kwargs)
else:
ret = self.func(*args, **kwargs)
print "after"
return ret
for attr in "__module__", "__name__", "__doc__":
setattr(wrapper, attr, getattr(self.func, attr))
if self.type == CLASS:
return types.MethodType(wrapper, cls, type)
elif self.type == NORMAL:
return types.MethodType(wrapper, obj, cls)
else:
return wrapper
def decorate_class(cls):
for name, meth in inspect.getmembers(cls):
if inspect.ismethod(meth):
if inspect.isclass(meth.im_self):
# meth is a classmethod
setattr(cls, name, DecoratedMethod(meth, CLASS))
else:
# meth is a regular method
setattr(cls, name, DecoratedMethod(meth, NORMAL))
elif inspect.isfunction(meth):
# meth is a staticmethod
setattr(cls, name, DecoratedMethod(meth, STATIC))
return cls
@decorate_class
class MyClass(object):
def __init__(self):
self.a = 10
print "__init__"
def foo(self):
print self.a
@staticmethod
def baz():
print "baz"
@classmethod
def bar(cls):
print "bar"
later I cleaned it up a bit, but that's the solution's essence. I need this difference between class, static and normal methods because I want to have
inst = MyClass()
assert type(inst.baz) == types.FunctionType
assert type(inst.foo) == types.MethodType
assert type(inst.bar) == types.MethodType
Why don't you alter the class object?
You can go through the methods in a class with dir(MyClass)
and replace them with a wrapped version... something like:
def logify(klass):
for member in dir(klass):
if not callable(getattr(klass, method))
continue # skip attributes
setattr(klass, method, log(method))
tinker around with something like this... should work...
A class decorator can help here. Decorate the whole class and add you logging functionality to all callable attributes the class have.
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