Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does self get bound in Python?

Tags:

python

self

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?

like image 577
Jeremy Avatar asked Sep 17 '25 14:09

Jeremy


1 Answers

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)
like image 81
abarnert Avatar answered Sep 19 '25 05:09

abarnert