2

I want to bind this function as a method on some class:

def foo_method(msg, self):
    print self.prefix + msg

This works:

class A(object):
    prefix = 'msg: '

A.foo = lambda *args: foo_method('hello', *args)

A().foo()
# => msg: hello

This, however, does not:

from functools import partial

class B(object):
    prefix = 'msg: '

B.foo = partial(foo_method, 'hello')

B().foo()
# => TypeError: foo_method() takes exactly 2 arguments (1 given)

The first example appears to be bound correctly, whereas the second does not.

Why?

9
  • 1
    Interesting. Investigating this now. I hope this is just an experiment you're playing around with and you're not actually trying to dynamically add partially applied methods to classes. Commented Jan 19, 2018 at 9:04
  • You don't need the setattr here. A.foo = lambda # ... also exhibits this behavior. Commented Jan 19, 2018 at 9:09
  • I’d venture this is some strange interaction due to lambda being a proper function and partial being a Callable (it’s actually implemented as a class). Commented Jan 19, 2018 at 9:14
  • @BaileyParker Yeah, I was guessing it had something to do with partial not being a function, I just haven't been able to find an explanation why that matters. Maybe it's something in the object model that I don't understand? Commented Jan 19, 2018 at 9:21
  • So it's somewhere in the self binding in __getattribute__. A().foo produces <bound method <lambda> of ...>> and B().foo produces functools.partial(<function foo_method at ...>, 'hello'). So it looks like in B __getattribute__ doesn't self bind. To get an answer you have to delve into the world of metaclasses. Commented Jan 19, 2018 at 9:23

1 Answer 1

3

The answer is: descriptors! Self-binding only happens during attribute access. There is no special language support for methods - they work using just function objects and descriptors.

Quoting https://docs.python.org/2/howto/descriptor.html#functions-and-methods:

Class dictionaries store methods as functions. [...] To support method calls, functions include the __get__() method for binding methods during attribute access. This means that all functions are non-data descriptors which return bound or unbound methods depending whether they are invoked from an object or a class. In pure python, it works like this:

class Function(object):
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        return types.MethodType(self, obj, objtype)

Fascinating!

When accessing A().foo, the descriptor mechanism is invoked, calling foo_method.__get__, which returns a bound method:

A().foo
# => <bound method A.<lambda> of <__main__.A object at 0x10ca9c0d0>>

Since functools.partial does not implement __get__, it does not get bound:

B().foo
# => <functools.partial at 0x10caf3aa0>

hasattr(partial(foo_method, 'hello'), '__get__')
# => False

The question remains: if it's so simple, why doesn't functools.partial implement this as well?

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.