Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Usage of functool.partialmethod and functool.partial?

Consider the following code:

import functools
import inspect

class Foo:
    def foo_fn(self, hello, world):
        print(hello, world)

class FooWrapper:
    def __init__(self, foo_class):
        self._foo = foo_class()
        for key, value in inspect.getmembers(self._foo):
            if inspect.ismethod(value):
                bound_fn = functools.partial(self.foo_wrapper_fn, self, value)
                setattr(self._foo, key, bound_fn)

    def foo_wrapper_fn(self_wrapper, self_foo, bound_method, hello, world):
        bound_method(hello, world)

def make_foo(Foo):
    wrapper = FooWrapper(Foo)
    return wrapper._foo
a = make_foo(Foo)
a.foo_fn("hello", "world")
  1. How does this ordering of the function foo_wrapper_fn() parameters like (self_wrapper, self_foo, bound_method, hello, world) come to be? and not like so: self_wrapper, bound_fn, self_foo, hello, world, since self_wrapper and bound_fn were partially bound first?

  2. Is it ok for the return of functool.partial (and not functool.partialmethod) to be called by an object (a.foo_fn)?

  3. If I replace it with functools.partialmethod(self.foo_wrapper_fn, self, value) why does this error come up?

    TypeError: 'partialmethod' object is not callable
    
like image 558
goldcode Avatar asked Dec 09 '16 23:12

goldcode


1 Answers

Both first arguments are the same object. self.foo_wrapper_fn is bound to the FooWrapper instance (as it was looked up on self), and then you tell the partial to pass in self again. So really, the signature is just a double confusion, and you can leave off the second argument and simply not pass in a second, explicit self.

Note that you are never passing in self._foo anywhere.

The order is simply set by:

  1. The bound method, passing in the bound FooWrapper() instance.
  2. Whatever positional arguments you passed into the partial() call, so self, value. That's the FooWrapper() instance again, and the bound Foo method.
  3. any additional arguments you pass in when calling the partial() object.

You have to use a partial() object here, not a partialmethod(), because you are setting attributes on an instance. A partialmethod() is intended to be used as a descriptor object, e.g. an attribute on the class.

This is why you get your TypeError too; the partialmethod() was never bound. If it was bound (it's __get__ method was called, passing in the instance to bind to), then a proper callable object would have been returned.

See the Python Descriptor Howto on how descriptors work (bound methods are created via that protocol, and partialmethod objects, as well as property, classmethod and staticmethod objects, all build on the same principles).

So you could simplify your code with:

class FooWrapper:
    def __init__(self, foo_class):
        self._foo = foo_class()
        for key, value in inspect.getmembers(self._foo):
            if inspect.ismethod(value):
                bound_fn = functools.partial(self.foo_wrapper_fn, value)
                setattr(self._foo, key, bound_fn)

    def foo_wrapper_fn(self, bound_method, hello, world):
        bound_method(hello, world)

If you must have access to the original Foo() instance, use the __self__ attribute on the bound_method variable.

like image 82
Martijn Pieters Avatar answered Nov 10 '22 10:11

Martijn Pieters