How does Python bind self
when calling functions on an object? I'm trying to understand why this:
class A(object):
def __init__(self):
self.name = 'class A'
def show_name(self):
print(self, self.name)
class B(object):
def __init__(self, a):
self.name = 'class B'
self.show_name = a.show_name
A().show_name()
B(A()).show_name()
outputs
(<self.A object at 0x7f9d35a06e50>, 'class A')
(<self.A object at 0x7f9d35a06e50>, 'class A')
Edit: How does it know that show_name
should be given the instance of class A
as its first (self
) parameter, not the instance of class B
?
For the full details, see this blog post. But I can summarize here.
The key is to understand bound methods. A bound method is just an object that holds the actual function object, and the instance it's bound to. Something like this:
class BoundMethod(object):
def __init__(self, function, instance):
self.__func__ = function
self.__self__ = instance
def __call__(self, *args, **kwargs):
return self.__func__(self.__self__, *args, **kwargs)
Bound methods are first-class objects, so you can pass them around and inspect them:
>>> class C:
... def foo(self):
... print(self)
>>> C.foo
<function __main__.foo>
>>> c = C()
>>> c.foo
<bound method C.foo of <__main__.C object at 0x10ab90a90>>
>>> c.foo.__func__
<function __main__.foo>
>>> c.foo.__func__ is C.foo, c.foo.__self__ is c
(True, True)
And you can even construct them manually:
>>> import types
>>> f = types.MethodType(C.foo, 2)
>>> f
<bound method int.foo of 2>
>>> f()
2
(While my Python class above obviously isn't the real code, it's not far off; you'll even get most of the same errors if you do silly things like try to bind a bound method or a non-callable.)
So, how does c.foo
end up bound to c
? To really understand that, you need to understand descriptors. But the short version is this:
Almost every type in Python has an extra method __get__
. And this is used for attribute access. Basically, when you type c.foo
, it does something like this (ignoring base classes, slots, getattr overrides, etc.):
try:
return c.__dict__['foo']
except KeyError:
value = type(c).__dict__['foo']
try:
return value.__get__(c)
except AttributeError:
return value
The __get__
method on a function object returns a bound method (bound to its argument).
So, going back to your example, obviously B(A()).show_name()
prints out stuff about A
because B(A()).show_name
is bound to an A
instance… but how does that happen?
Well, let's trace through this:
>>> a = A()
>>> b = B(a)
>>> b.show_name
Inside your __init__
function, you do this:
self.show_name = a.show_name
That's just copying the already-bound method a.show_name
to a normal instance variable self.show_name
. So, when you later look up b.show_name
, it's the same bound method, bound to a
. If you want to re-bind it, you'll have to do it manually, something like:
self.show_name = types.MethodType(a.show_name.__func__, self)
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