You mentioned that you want to intercept the method calls during normal operations and not just during debugging. This solution is a different approach then the other answer, it uses the Proxy pattern approach of making a new object that will be a sort of wrapper to all the calls to the original object. On the proxy level you can log and monitor the calls as needed.
person.py
To demonstrate the code I've created an example class called Person
in person
module. We can pretend this is the module which you cannot modify the code.
class Person(object):
def __init__(self, name=None):
self.name = name
def greet(self, greeting='hello'):
print '%s %s' % (greeting, self.name)
def __repr__(self):
return "Person(%r)" % self.name
example.py
In the code below we create a proxy object called ProxyPerson
which when used instead of Person
behaves identically to it and has the added behavior of logging all calls to the greet method. You notice that it uses *args, **kwargs
so Person might have a different signature in the future but the code won't break if changes are made.
import person
class ProxyPerson(person.Person):
def greet(self, *args, **kwargs):
print '--- greet() self=%r args=%r kwargs=%r' % (self, args, kwargs)
super(ProxyPerson, self).greet(*args, **kwargs)
#person.Person = ProxyPerson #uncomment to monkey patch Person
jack, jill = person.Person('Jack'), ProxyPerson('Jill')
for p in jack, jill:
p.greet()
p.greet('hi')
p.greet(greeting='why hello')
Regular output
The output below shows the difference in execution when the original Person class is is used or when the ProxyPerson is used. The examples also include calls with no positional arguments, with positional arguments, and finally with keyword arguments.
hello Jack
hi Jack
why hello Jack
--- greet() self=Person('Jill') args=() kwargs={}
hello Jill
--- greet() self=Person('Jill') args=('hi',) kwargs={}
hi Jill
--- greet() self=Person('Jill') args=() kwargs={'greeting': 'why hello'}
why hello Jill
Monkey patched output
Finally one can Monkey patch person.Person
class to point to ProxyPerson. You can experiment with this approach by commenting out the monkey patch line in example.py
. The output in this case would be as follows:
--- greet() self=Person('Jack') args=() kwargs={}
hello Jack
--- greet() self=Person('Jack') args=('hi',) kwargs={}
hi Jack
--- greet() self=Person('Jack') args=() kwargs={'greeting': 'why hello'}
why hello Jack
--- greet() self=Person('Jill') args=() kwargs={}
hello Jill
--- greet() self=Person('Jill') args=('hi',) kwargs={}
hi Jill
--- greet() self=Person('Jill') args=() kwargs={'greeting': 'why hello'}
why hello Jill
The benefits of monkey patching are that all future instances of person.Person will in fact be instances of the proxy object that has been created. Monkey patching comes with it's own set of issues that you should keep in mind before using it.