I'm looking for a means to trace all calls on an instance of a class, including calls to dunder methods like __iter__
, __getattribute__
etc. I've tried using sys.setprofile
, the autologging
package, but failed : it doesn't seem to be able to trace calls to dundermethods, or attributes etc.
My usecase is the following. Python's duck typing is awesome, and I would need to be able to "mock" objects, implementing a class only with the necessary methods (including magic or dunders). As I understand duck typing (tell me if i'm misleading), it's basically the fact that the following code will work for any object instance_of_class
as long as it implements the methods necessary for iterables (__iter__
and __next__
for example) and that elements returned implements object.__getattribute__('name')
which result has __repr__
method, for the printing
import ExternalClass
ExternalClass = my_awesome_decorator_to_trace_them_all(ExternalClass)
instance_of_class = ExternalClass(...)
for element in instance_of_class:
print(element.name)
So, I would like to be able to trace variables (or the original class, and then every instances will get traced), using for example a decorator or anything, that would show the list of all methods called. In the previous example for ExternalClass
, it would be:
method __init__ called with arguments ...
method __new__ called with arguments ...
method __iter__ called with arguments ...
method __next__ called with arguments ...
Thanks for the help
--- Update with @martineau efficient and elegant solution
From @martineau's working code on How to wrap every method of a class?
from functools import wraps
from types import FunctionType
def wrapper(method):
@wraps(method)
def wrapped(*args, **kwargs):
class_name = args[0].__class__.__name__
func_name = method.__name__
print('calling {}.{}()... '.format(class_name, func_name))
return method(*args, **kwargs)
return wrapped
class MetaClass(type):
def __new__(meta, classname, bases, classDict):
newClassDict = {}
for attributeName, attribute in classDict.items():
if isinstance(attribute, FunctionType):
# replace it with a wrapped version
attribute = wrapper(attribute)
newClassDict[attributeName] = attribute
return type.__new__(meta, classname, bases, newClassDict)
class SimpleList(object, metaclass=MetaClass):
def __init__(self):
super().__init__()
self.list_elem = list(range(1))
def __iter__(self):
self.index = 0
return self
def __next__(self):
this_index = self.index
if this_index >= len(self.list_elem):
raise StopIteration()
self.index += 1
return self.list_elem[this_index]
simple_list = SimpleList()
print(simple_list)
for element in simple_list:
pass
prints the following
calling SimpleList.__init__()...
<__main__.SimpleList object at 0x1054ce9a0>
calling SimpleList.__iter__()...
calling SimpleList.__next__()...
calling SimpleList.__next__()...