-2

I have a basic question on how a method call really works in Python. I found this post and this one that are helpful, but not answering all my questions.

Basically, my understanding from Section 9.3.4 of Python documentation is that:

When an instance object calls its method, a search is done on the class object. If the matching function is found, the function object attached to the class object is called with the instance object as its first parameter.

My main questions are:

  • What is the significance of bound methods? Currently, a call to method is transferred to a call to function with the instance object as its first argument enabling the function to access the instance object. Why has Python adopted this approach, instead of each method directly processing its bound instance object? Any cons and pros for each of these approaches?

  • Can a bound method theoretically lead to a lower performance (slower execution), in case that a large number of instances call their own method simultaneously, which is seemingly equivalent to calling one single function object multiple times (in a serial fashion??) with different instances as the first argument per call?

I know I am missing some fundamentals here, and I would be grateful if anyone could please advise. Thank you for your time.

learner
  • 603
  • 1
  • 3
  • 15

1 Answers1

1

You skipped a step in what actually happens. Let's say you try to call foo.bar(baz), where foo is an instance of Foo.

  1. First, an instance attribute named bar is looked for in the object foo. Most likely, it doesn't exist, as you typically don't want to shadow the actual method like this.

  2. Next, bar is looked for as a class attribute in each class in the MRO of foo. For simplicity, assume you find it right away in Foo.

  3. At this point, Foo.bar is a simple function object. But, because a function implements the descriptor protocol, you don't actually get the function object itself: you get the result of Foo.bar.__get__(foo, Foo). That result is the bound method: an instance of method which wraps both the original function and the object foo. (For a better description of what is going on, see the descriptor protocol how-to guide.

  4. Calling an instance of method causes the underlying function to be called, with foo as the first argument and baz as an additional object. Whatever the function returns is what the method instance returns.

This is how foo.bar(baz) becomes Foo.bar(foo, baz), by way of Foo.bar.__get__(foo, Foo)(baz).

Although there is some additional overhead, it's not really significant compared to the benefit of having many different things simply be applications of the descriptor protocol. All of the following are implemented via descriptors, without having to add specific implementations of each to the language itself:

  1. Instance methods
  2. Class methods
  3. Static methods
  4. Properties

as well as the descriptor protocol being something you can implement in your own classes in order to provide specific behavior in a way that blends seamlessly with other Python conventions.

chepner
  • 497,756
  • 71
  • 530
  • 681