Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use a callable object as a method definition?

I know it's possible to define a function and use it as a method:

def method(*args, **kwargs):
    print "%r %r" % (args, kwargs)

class Test(object):
    method1 = method

t = Test()
t.method1()  # (<__main__.Test object at 0x10705a4d0>,) {}

I'd like to do the same thing with callable objects, like this:

class Method(object):
    __call__ = method

class Test(object):
    method1 = Method()

However, when I do this, the self argument of Method.__call__ is the method itself (which is normal), but the self argument of the Test instance is lost.

t = Test()
t.method1()  # (<__main__.Method object at 0x10703f110>,) {}

Is it possible to have the self argument passed as the second argument to Method.__call__?

like image 850
zneak Avatar asked Jan 02 '23 11:01

zneak


2 Answers

By wrapping that function method in a class you are effectively preventing the mechanism that binds an object to a function and thus creates a method. The way this works is that regular python functions are descriptors.

To summarize the docs: When you write the following code:

some_instance.some_function()

The some_functions __get__ method is called with some_instance as the first parameter. The __get__ method then returns a bound method object, that remembers the instance. Later, when the bound method object's __call__ method is called, it passes the saved instance as a first parameter.

We can reimplement that behaviour like this:

def method(*args, **kwargs):
    print("%r %r" % (args, kwargs))


class BoundMethod(object):
    # the bound method remembers the instance and the function
    def __init__(self, instance, function):
        self.instance = instance
        self.function = function

    # when the bound method is called, it passes the instance
    def __call__(self, *args, **kwargs):
        return self.function(self.instance, *args, **kwargs)


class Method(object):
    # the __get__ method assembles a bound method consisting of the
    # instance it was called from and the function
    def __get__(self, instance, cls):
        return BoundMethod(instance, method)


class Test(object):
    method1 = Method()


t = Test()
t.method1()  # (<__main__.Test object at 0x7f94d8c3aad0>,) {} 

In your case Method is not a descriptor. So, when internally the __call__ property (which is a function) is requested it is bound to an object of the containing class (Method).

I am not sure if this is useful, as this example is just a simplified version of what happens under the hood anyway.

Note: in this example:

class C:
    def function(self): pass

print(C.function)
print(C().function)

The first print shows us, that an unbound method literally is called <unbound method C.function> while a bound method is called <bound method C.function of ...>.

In python3 however the first print shows us that unbound methods are just the unchanged functions we defined in the class.

like image 65
Wombatz Avatar answered Jan 08 '23 17:01

Wombatz


Yes: make the method a descriptor—which, as already noted by Wombatz, is the mechanism used by normal method binding.

class Method(object):
  def __get__(self,obj,cls):
    def method(*args, **kwargs):
      print type(obj)   # to illustrate what object we get
      print type(self)  # we have the Method too
      print "%r %r" % (args, kwargs)
    return method

Notes:

  1. It would also be possible to forward to the __get__ on a function and produce a legitimate bound method object, but this way you have self and obj available.
  2. obj is None if the method is looked up on the class itself (Test.method1, rather than Test().method1); you can decide separately what to do (with cls) in that case.
like image 39
Davis Herring Avatar answered Jan 08 '23 17:01

Davis Herring