I'm trying to intercept calls to python's double underscore magic methods in new style classes. This is a trivial example but it show's the intent:
class ShowMeList(object): def __init__(self, it): self._data = list(it) def __getattr__(self, name): attr = object.__getattribute__(self._data, name) if callable(attr): def wrapper(*a, **kw): print "before the call" result = attr(*a, **kw) print "after the call" return result return wrapper return attr
If I use that proxy object around list I get the expected behavior for non-magic methods but my wrapper function is never called for magic methods.
>>> l = ShowMeList(range(8)) >>> l #call to __repr__ <__main__.ShowMeList object at 0x9640eac> >>> l.append(9) before the call after the call >> len(l._data) 9
If I don't inherit from object (first line class ShowMeList:
) everything works as expected:
>>> l = ShowMeList(range(8)) >>> l #call to __repr__ before the call after the call [0, 1, 2, 3, 4, 5, 6, 7] >>> l.append(9) before the call after the call >> len(l._data) 9
How do I accomplish this intercept with new style classes?
__new__ in Python Dunder or magic methods in Python are the methods having two prefix and suffix underscores in the method name. These are commonly used for operator overloading. Few examples for magic methods are: __init__ , __add__ , __len__ , __repr__ etc. Note: To know more about Magic methods click here.
The __call__ method enables Python programmers to write classes where the instances behave like functions. Both functions and the instances of such classes are called callables.
Dunder methods are names that are preceded and succeeded by double underscores, hence the name dunder. They are also called magic methods and can help override functionality for built-in functions for custom classes.
For performance reasons, Python always looks in the class (and parent classes') __dict__
for magic methods and does not use the normal attribute lookup mechanism. A workaround is to use a metaclass to automatically add proxies for magic methods at the time of class creation; I've used this technique to avoid having to write boilerplate call-through methods for wrapper classes, for example.
class Wrapper(object): """Wrapper class that provides proxy access to an instance of some internal instance.""" __wraps__ = None __ignore__ = "class mro new init setattr getattr getattribute" def __init__(self, obj): if self.__wraps__ is None: raise TypeError("base class Wrapper may not be instantiated") elif isinstance(obj, self.__wraps__): self._obj = obj else: raise ValueError("wrapped object must be of %s" % self.__wraps__) # provide proxy access to regular attributes of wrapped object def __getattr__(self, name): return getattr(self._obj, name) # create proxies for wrapped object's double-underscore attributes class __metaclass__(type): def __init__(cls, name, bases, dct): def make_proxy(name): def proxy(self, *args): return getattr(self._obj, name) return proxy type.__init__(cls, name, bases, dct) if cls.__wraps__: ignore = set("__%s__" % n for n in cls.__ignore__.split()) for name in dir(cls.__wraps__): if name.startswith("__"): if name not in ignore and name not in dct: setattr(cls, name, property(make_proxy(name)))
Usage:
class DictWrapper(Wrapper): __wraps__ = dict wrapped_dict = DictWrapper(dict(a=1, b=2, c=3)) # make sure it worked.... assert "b" in wrapped_dict # __contains__ assert wrapped_dict == dict(a=1, b=2, c=3) # __eq__ assert "'a': 1" in str(wrapped_dict) # __str__ assert wrapped_dict.__doc__.startswith("dict()") # __doc__
Using __getattr__
and __getattribute__
are the last resources of a class to respond to getting an attribute.
Consider the following:
>>> class C: x = 1 def __init__(self): self.y = 2 def __getattr__(self, attr): print(attr) >>> c = C() >>> c.x 1 >>> c.y 2 >>> c.z z
The __getattr__
method is only called when nothing else works (It will not work on operators, and you can read about that here).
On your example, the __repr__
and many other magic methods are already defined in the object
class.
One thing can be done, thought, and it is to define those magic methods and make then call the __getattr__
method. Check this other question by me and its answers (link) to see some code doing that.
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