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")
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?
Is it ok for the return of functool.partial
(and not functool.partialmethod
) to be called by an object (a.foo_fn
)?
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
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:
FooWrapper()
instance.partial()
call, so
self, value
. That's the FooWrapper()
instance again, and the bound Foo
method.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With