I'm working with quite a large OOP code-base, and I'd like to inject some tracing/logging. The easiest way to do this would be to introduce a decorator around certain methods on some base classes, but unfortunately decorators aren't inherited.
I did try something like the following:
def trace(fn):
def wrapper(instance, *args, **kwargs):
result = fn(instance, *args, **kwargs)
# trace logic...
return result
return wrapper
class BaseClass(object):
def __init__(self, ...):
...
self.__call__ = trace(self.__call__) # line added to end of method
...and while the __call__
method is wrapped (which I can see from printing the instance information to the console) the wrapper
function isn't executed as expected.
I also looked briefly at using a metaclass based on this answer, but it instantly breaks other parts of the system that use introspection, so I think that's a no-go.
Is there any other way I can force the application of these decorators around the __call__
method of classes that inherit from BaseClass
?
Why would metaprogramming mess up introspection? Perhaps you are not using it correctly? Try this (assuming Python2.x):
class MyMeta(type):
def __new__(mcl, name, bases, nmspc):
if "__call__" in nmspc:
nmspc["__call__"] = trace(nmspc["__call__"])
return super(MyMeta, mcl).__new__(mcl, name, bases, nmspc)
class BaseClass(object):
__metaclass__ = MyMeta
You can then simply inherit from BaseClass
and __call__
will get wrapped automatically.
I'm not sure what kind of introspection would break that. Unless BaseClass
actually does not inherit from object
but from something that implements its own meta?? But you can deal with that case as well by forcing MyMeta
to inherit from that parent's meta (instead of type
).
You can't do that... see things like here: Python method resolution mystery
Basically. most of special functions are looked up at the class level. Monkey-patching it after initialization won't have any effect.
That said, you should be able to do something like this:
class BaseClass(object):
__call__ = trace(object.__call__)
def __init__(self, ...):
...
Now, it's wrapped at the class level, so it will work.
edit: That's not really an answer. What you want is something like:
class BaseClass(object):
@decorator
__call__ = something
class SubClass(BaseClass):
__call__ = something_else
In this case... it doesn't matter what you put for the BaseClass __call__
method, the SubClass method will override it. It's not about decorators vs. non-decorators, as it's functions (well, really, attributes, since there isn't really much difference in python) that get inherited. Decorators look separate from the function, but they aren't really - in the above, all that the decorator does is change the definition of __call__
from something
to decorator(something)
(which must still return a callable).
Then, in the subclass, you reassign this function to something completely different.
You could use a class decorator, but those things tend to break introspection as you noticed; they turn a class, into a function that returns a class. Similar problem with factory functions.
Maybe look into the profile
module? I know there are some lower-level hooks that it uses to track function calls etc. that you might be able to use as well (though don't know much about them).
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