Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I intercept calls to python's "magic" methods in new style classes?

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?

like image 929
Finn Avatar asked Jan 29 '12 23:01

Finn


People also ask

What is new magic method in Python?

__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.

What are __ methods in Python?

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.

What are the Dunder Magic special methods in Python?

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.


2 Answers

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__ 
like image 163
13 revs, 2 users 99% Avatar answered Oct 14 '22 06:10

13 revs, 2 users 99%


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.

like image 22
JBernardo Avatar answered Oct 14 '22 08:10

JBernardo