Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instance of Python class that responds to all method calls

Tags:

python

Is there a way to create a class which instances respond to arbitrary method calls?

I know there is a the special method __getattr__(self, attr) which would be called when someone is trying to access an attribute of an instance. I am searching for something similar that enables me to intercept method calls, too. The desired behavior would look something like this:

class A(object):
    def __methodintercept__(self, method, *args, **kwargs): # is there a special method like this??
        print(str(method))


>>> a = A()
>>> a.foomatic()
foomatic

EDIT

The other suggested questions do not address my case: I do not want to wrap another class or change the metaclass of a second class or similar. I just want to have a class that responds to arbitrary method calls.

Thanks to jonrshape I now know that __getattr__(self, attr) will also be called when a method is called in the same way as it would be when an attribute is accessed. But how do i distinguish in __getattr__ if attr comes from a method call or an attribute access and how to get the parameters of a potential method call?

like image 534
Salo Avatar asked Oct 14 '15 12:10

Salo


3 Answers

This is the solution I was looking for when coming across this question:

class Wrapper:
    def __init__(self):
        self._inner = []  # or whatever type you want to wrap

    def foo(self, x):
        print(x)

    def __getattr__(self, attr):
        if attr in self.__class__.__dict__:
            return getattr(self, attr)
        else:
            return getattr(self._inner, attr)

t = Test()
t.foo('abc')  # prints 'abc'
t.append('x')  # appends 'x' to t._inner

Criticisms very welcome. I wanted to add methods to the Browser class in the Splinter package, but it only exposes a function to return an instance, not the class itself. This approach permitted pseudo-inheritance, which meant I could declaratively decouple DOM code from website-specific code. (A better approach in hindsight might have been to use Selenium directly.)

like image 104
Chris Avatar answered Nov 19 '22 06:11

Chris


unittest.mock.Mock does this by default.

from unittest.mock import Mock

a = Mock()

a.arbitrary_method()                             # No error
a.arbitrary_method.called                        # True
a.new_method
a.new_method.called                              # False
a.new_method("some", "args")
a.new_method.called                              # True
a.new_method.assert_called_with("some", "args")  # No error
a.new_method_assert_called_with("other", "args") # AssertionError
like image 24
Adam Smith Avatar answered Nov 19 '22 08:11

Adam Smith


This is something I came up with, which will behave exactly as if the method exists.

First let's establish one thing: You cannot distinguish in __getattr__ if attr comes from a function call or an "attribute access", because a class method is an attribute of your class. So someone can access that method even if they don't intend to call it, as in:

class Test:
    def method(self):
        print "Hi, I am method"

>> t = Test()
>> t.method # just access the method "as an attribute"
<bound method Test.method of <__main__.Test instance at 0x10a970c68>>

>> t.method() # actually call the method
Hi, I am method

Therefore, the closest thing I could think of is this behavior:

Create a class A, such that:

  1. When we try to access an attribute / method, which already exists in that class, act normal and just return the requested attribute / method.
  2. When we try to access something that doesn't exist in the class definition, treat it as a class method and have 1 global handler for all such methods.

I will first write the class definition and then show how accessing a method that doesn't exist behaves exactly like accessing one that exists, whether you are just accessing it, or actually calling it.

Class definition:

class A(object):
    def __init__(self):
        self.x = 1 # set some attribute

    def __getattr__(self,attr):
        try:
            return super(A, self).__getattr__(attr)
        except AttributeError:
            return self.__get_global_handler(attr)

    def __get_global_handler(self, name):
        # Do anything that you need to do before simulating the method call
        handler = self.__global_handler
        handler.im_func.func_name = name # Change the method's name
        return handler

    def __global_handler(self, *args, **kwargs):
        # Do something with these arguments
        print "I am an imaginary method with name %s" % self.__global_handler.im_func.func_name
        print "My arguments are: " + str(args)
        print "My keyword arguments are: " + str(kwargs)

    def real_method(self, *args, **kwargs):
        print "I am a method that you actually defined"
        print "My name is %s" % self.real_method.im_func.func_name
        print "My arguments are: " + str(args)
        print "My keyword arguments are: " + str(kwargs)

I added the method real_method just so I have something that actually exists in the class to compare its behavior with that of an 'imaginary method'

Here's the result:

>> a = A() 
>> # First let's try simple access (no method call)
>> a.real_method # The method that is actually defined in the class
<bound method A.real_method of <test.A object at 0x10a9784d0>>

>> a.imaginary_method # Some method that is not defined
<bound method A.imaginary_method of <test.A object at 0x10a9784d0>>

>> # Now let's try to call each of these methods
>> a.real_method(1, 2, x=3, y=4)
I am a method that you actually defined
My name is real_method
My arguments are: (1, 2)
My keyword arguments are: {'y': 4, 'x': 3}

>> a.imaginary_method(1, 2, x=3, y=4)
I am an imaginary method with name imaginary_method
My arguments are: (1, 2)
My keyword arguments are: {'y': 4, 'x': 3}

>> # Now let's try to access the x attribute, just to make sure that 'regular' attribute access works fine as well
>> a.x
1
like image 13
tomas Avatar answered Nov 19 '22 07:11

tomas