1

Recently, I was looking into the "getattr" method, and one interesting behaviour is that it could return a method of a class instance and assign it to a variable. Here's the example I played with:

class Example:
    def some_method(self, param1):
        print(param1)

example = Example()
method1 = getattr(example, "some_method")
print(method1)
method1("hi")

And here's the output:

<bound method Example.some_method of <__main__.Example object at 0x7f82cbcb2850>>
hi

I do understand for the first line of the output that the method1 is a "bound method" type related to the actual instance example. But for the function call involving the method method1("hi"), the "self" parameter is omitted, only the "param1" value is given. I wonder how this function call is processed successfully and where the "self" parameter is actually stored internally.

T_Y
  • 11
  • 1
  • 1
    The same way `example.some_method('hi')` works – juanpa.arrivillaga Nov 23 '21 at 06:36
  • There are many questions about `self`, for example https://stackoverflow.com/q/2709821/407651. The quality of the answers varies, but are you really asking a question that hasn't been asked and answered already? – mzjn Nov 23 '21 at 07:07

1 Answers1

0

Methods rely on the mechanism called descriptors. Any type can be made a descriptor by implementing methods such as __get__. For example:

class D:
    def __get__(self, obj, type=None):
        return f'Some value from {obj.__class__.__name__}'

class A:
    d = D()

print(A().d)  # 'Some value from A'

Python attribute access mechanism will recognize the descriptor and delegate the access logic to its __get__(), __set__(), etc. Now, the plot twist is that Python functions also implement the descriptor protocol, that defines how they behave when accessed as methods. For example, if we have a method A.f():

class A:
    def __init__(self, x):
       self.x = x

    def f(self, y):
        return self.x + y

then this code:

a = A(2)
print(a.f(3))  # prints 5

in fact runs as:

a = A(2)
bound_f = A.__get__(a)
print(bound_f(3))   # also prints 5

So when a function is accessed as a non-static method, it intercepts the access via te descriptor protocol and returns a bound method object, with attribute __self__ set to the object on which it was accessed:

>>> print(bound_f)
<bound method A.f of <__main__.A object at 0x7fc13db99ac0>>
>>> print(bound_f.__self__)
<__main__.A at 0x7fc13db99ac0>

Static, class methods and properties are also implemented using descriptors.

bereal
  • 32,519
  • 6
  • 58
  • 104