Note: the following abstraction does not work with C extension code that goes directly into the low-level internals of the wrapped object (such as heapq.heappush
, both on CPython as well as PyPy); there's nothing that can be done to alleviate that at the Python level. You might see if you can "patch the leak" at the C level, but then you'll have to get your hands dirty with writing C and a Python extension.
Solution: You don't need to go as far as __new__
. The following will work generically on all objects. It will also make isinstance
work on the wrapper as if it were called on the wrapped object.
from functools import wraps
class Logged(object):
def __init__(self, obj, obj_name):
self.obj = obj
self.obj_name = obj_name
def __getattribute__(self, attr_name):
obj = object.__getattribute__(self, 'obj')
obj_name = object.__getattribute__(self, 'obj_name')
attr = getattr(obj, attr_name)
# this is not 100% generic, mathematically speaking,
# but covers all methods and the `__class__` attribute:
if not callable(attr) or isinstance(attr, type):
return attr
@wraps(attr)
def fn(*args, **kwargs):
print "%s called on %s with: %s and %s" % (attr_name, obj_name, args, kwargs)
return attr(*args, **kwargs)
return fn
def __repr__(self):
return repr(object.__getattribute__(self, 'obj'))
And then just:
>>> scheduled = Logged([], obj_name="scheduled")
>>> scheduled.append
<function append>
>>> scheduled.append(3)
append called on scheduled with: (3,) and {}
>>> scheduled.extend([1,2])
extend called on scheduled with: ([1, 2],) and {}
>>> isinstance(scheduled, list)
True
>>> scheduled
[3, 1, 2]