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?
setattrhere.A.foo = lambda # ...also exhibits this behavior.__getattribute__.A().fooproduces<bound method <lambda> of ...>>andB().fooproducesfunctools.partial(<function foo_method at ...>, 'hello'). So it looks like inB__getattribute__doesn't self bind. To get an answer you have to delve into the world of metaclasses.