Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to catch any method called on an object in python?

I'm looking for a pythonic solution on how to store a method which is called on an object right inside the object.

Because in python, if I want to catch for example the abs() method, I will overload this operator like:

Catcher(object):
    def __abs__(self):
        self.function = abs

c = Catcher()
abs(c)  # Now c.function stores 'abs' as it was called on c

If I want to catch a function, which have an other attribute in it, for example pow(), I'm going to use this:

Catcher(object):
    def __pow__(self, value):
        self.function = pow
        self.value = value

c = Catcher()
c ** 2  # Now c.function stores 'pow', and c.value stores '2'

Now, what I'm looking for is a general solution, to catch and store any kind of function called on Catcher, without implementing all overloads, and other cases. And as You can see, I also want to store the values (maybe in a list, if there is more than one of them?) which are the attributes of a method.

Thanks in advance!

like image 773
Peter Varo Avatar asked May 04 '13 08:05

Peter Varo


People also ask

How do you call a method from an object in Python?

To call a class method, put the class as the first argument. Class methods can be can be called from instances and from the class itself. All of these use the same method. The method can use the classes variables and methods.

How do you find the method of an object?

The first method to find the methods is to use the dir() function. This function takes an object as an argument and returns a list of attributes and methods of that object. From the output, we can observe that it has returned all of the methods of the object.

What happens when you call a method on an object?

Calling an object's method is similar to getting an object's variable. To call an object's method, simply append the method name to an object reference with an intervening '. ' (period), and provide any arguments to the method within enclosing parentheses.

What is a Python method?

This means that it can deal with classes and objects to model the real world. A Python method is a label that you can call on an object; it is a piece of code to execute on that object. But before we begin getting any deeper, let’s take a quick look at classes and objects.

How to find out the methods of a Python object?

Lastly, we can also use the hasattr () method to find out the methods of a Python object. This function checks if an object has an attribute. The function takes two arguments: object and attribute. It checks if the attribute is present in that particular object.

What is the difference between function and method in Python?

Python Functions vs Method 1 While a method is called on an object, a function is generic. 2 Since we call a method on an object, it is associated with it. Consequently, it is able to access and operate on the... 3 A method may alter the state of the object; a function does not, when an object is passed as an argument to it. We have... More ...

How to create a callable object in Python?

So, To create a callable object in python, we will implement the __call__() method in the function definition of the object as seen in the example given abve.


2 Answers

A metaclass won't help here; although special methods are looked up on the type of the current object (so the class for instances), __getattribute__ or __getattr__ are not consulted when doing so (probably because they are themselves special methods). So to catch all dunder methods, you are forced to create them all.

You can get a pretty decent list of all operator special methods (__pow__, __gt__, etc.) by enumerating the operator module:

import operator
operator_hooks = [name for name in dir(operator) if name.startswith('__') and name.endswith('__')]

Armed with that list a class decorator could be:

def instrument_operator_hooks(cls):
    def add_hook(name):
        operator_func = getattr(operator, name.strip('_'), None)
        existing = getattr(cls, name, None)

        def op_hook(self, *args, **kw):
            print "Hooking into {}".format(name)
            self._function = operator_func
            self._params = (args, kw)
            if existing is not None:
                return existing(self, *args, **kw)
            raise AttributeError(name)

        try:
            setattr(cls, name, op_hook)
        except (AttributeError, TypeError):
            pass  # skip __name__ and __doc__ and the like

    for hook_name in operator_hooks:
        add_hook(hook_name)
    return cls

Then apply that to your class:

@instrument_operator_hooks
class CatchAll(object):
    pass

Demo:

>>> c = CatchAll()
>>> c ** 2
Hooking into __pow__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in op_hook
AttributeError: __pow__
>>> c._function
<built-in function pow>
>>> c._params
((2,), {})

So, even though our class doesn't define __pow__ explicitly, we still hooked into it.

like image 122
Martijn Pieters Avatar answered Oct 23 '22 23:10

Martijn Pieters


This is a way to do it.

import inspect
from functools import wraps
from collections import namedtuple

call = namedtuple('Call', ['fname', 'args', 'kwargs'])
calls = []

def register_calls(f):
    @wraps(f)
    def f_call(*args, **kw):
        calls.append(call(f.__name__, args, kw))
        print calls
        return f(*args, **kw)
    return f_call


def decorate_methods(decorator):
    def class_decorator(cls):
        for name, m in inspect.getmembers(cls, inspect.ismethod):
            setattr(cls, name, decorator(m))
        return cls
    return class_decorator


@decorate_methods(register_calls)
class Test(object):

    def test1(self):
        print 'test1'

    def test2(self):
        print 'test2'

Now all the calls to test1 and test2 will be registers in the calls list.

decorate_methods applies a decorator to each method of the class. register_calls registers the calls to the methods in calls, with the name of the function and the arguments.

like image 44
Martin Maillard Avatar answered Oct 24 '22 00:10

Martin Maillard