1

I created some test code, but I can't really understand why it works.

Shouldn't moo be defined before we can use it?

#!/usr/bin/python3

class Test():
    def __init__(self):
        self.printer = None

    def foo(self):
        self.printer = self.moo
        self.printer()

    def moo(self):
        print("Y u printing?")

test = Test()

test.foo()

Output:

$ python test.py
Y u printing?

I know that the rule is define earlier, not higher, but in this case it's neither of those.

scharette
  • 9,437
  • 8
  • 33
  • 67
Dani M
  • 1,173
  • 1
  • 15
  • 43
  • 1
    On instantiation of the class, it defines all of the methods first, then runs `__init__`, so the order of functions in a class technically doesn't matter – C.Nivs Jul 19 '18 at 13:10
  • 2
    [This question is being discussed on meta.](https://meta.stackoverflow.com/questions/371293/how-to-deal-with-great-but-wrongly-asked-questions) – Script47 Jul 19 '18 at 14:58
  • 1
    @scharette Yes, that's right, I supposed that the method been inside a class had to do something, but I wasn't sure – Dani M Jul 20 '18 at 06:42
  • 1
    @Cuoka I think I was wrong on the title change. Even tough it made sense in the context of the question, it can be misleading since defining order doesn't technically matter in python since defining `moo` before `foo` outside a class would still be valid if it is before the calling statement. Your concerns are more related to class specifically. I wanted to underline something but I will rollback the title change but keep the question body change which is now a lot clearer than before. Sorry for the misunderstanding and let me know if you don't agree. – scharette Jul 20 '18 at 15:21
  • @scharette I'm no expert in python (as you can see) so if you think this is a better tittle then it's fine by me. – Dani M Jul 20 '18 at 16:05
  • @cuoka I think it's better than mine. But note that I won't pretend I'm an expert either. We can always improve. Answering your question had me learning a lot of stuff also. Thanks for that. – scharette Jul 20 '18 at 16:06

2 Answers2

3

There's really nothing to be confused about here.

We have a function that says "when you call foo with a self parameter, look up moo in self's namespace, assign that value to printer in self's namespace, look up printer in self's namespace, and call that value".1

Unless/until you call that function, it doesn't matter whether or not anyone anywhere has an attribute named moo.

When you do call that method, whatever you pass as the self had better have a moo attribute or you're going to get an AttributeError. But this is no different from looking up an attribute on any object. If you write def spam(n): return n.bit_length() as a global function, when you call that function, whatever you pass as the n had better have a bit_length attribute or you're going to get an AttributeError.

So, we're calling it as test.foo(), so we're passing test as self. If you know how attribute lookup works (and there are already plenty of questions and answers on SO about that), you can trace this through. Slightly oversimplified:

  • Does test.__dict__ have a 'moo'? No.
  • Does type(test).__dict__ have a 'moo'? Yes. So we're done.

Again, this is the same way we check if 3 has a bit_length() method; there's no extra magic here.

That's really all there is to it.


In particular, notice that test.__dict__ does not have a 'moo'. Methods don't get created at construction time (__new__) any more than they get created at initialization time (__init__). The instance doesn't have any methods in it, because it doesn't have to; they can be looked up on the type.2

Sure, we could get into descriptors, and method resolution order, and object.__getattribute__, and how class and def statements are compiled and executed, and special method lookup to see if there's a custom __getattribute__, and so on, but you don't need any of that to understand this question.


1. If you're confused by this, it's probably because you're thinking in terms of semi-OO languages like C++ and its descendants, where a class has to specify all of its instances' attributes and methods, so the compiler can look at this->moo(), work out that this has a static type ofFoo, work out thatmoois the third method defined onFoo, and compile it into something likethis->vptr2`. If that's what you're expecting, forget all of it. In Python, methods are just attributes, and attributes are just looked up, by name, on demand.

2. If you're going to ask "then why is a bound method not the same thing as a function?", the answer is descriptors. Briefly: when an attribute is found on the type, Python calls the value's __get__ method, passing it the instance, and function objects' __get__ methods return method objects. So, if you want to refer specifically to bound method objects, then they get created every time a method is looked up. In particular, the bound method object does not exist yet when we call foo; it gets created by looking up self.moo inside foo.

abarnert
  • 354,177
  • 51
  • 601
  • 671
1

While all that @scharette says is likely true (I don't know enough of Python internals to agree with confidence :) ), I'd like to propose an alternative explanation as to why one can instantiate Test and call foo():

The method's body is not executed until you actually call it. It does not matter if foo() contains references to undefined attributes, it will be parsed fine. As long as you create moo before you call foo, you're ok.

Try entering a truncated Test class in your interpreter:

   class Test():
        def __init__(self):
            self.printer = None
        def foo(self):
            self.printer = self.moo
            self.printer()

No moo, so we get this:

>>> test = Test()
>>> test.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in foo

Let's add moo to the class now:

>>> def moo(self):
...     print("Y u printing?")
...
>>> Test.moo = moo
>>> test1 = Test()
>>> test1.foo()
Y u printing?
>>>

Alternatively, you can add moo directly to the instance:

>>> def moo():
...     print("Y u printing?")
...
>>> test.moo = moo
>>> test.foo()
Y u printing?

The only difference is that the instance's moo does not take a self (see here for explanation).

  • 1
    @scharette, I've just tried this in Python console with plain regular function and it worked. `def foo: moo();` followed by `foo()` produces error. Then `def moo(): print("all well");` followed by `foo()` produces `all well`. So it's not limited to class methods. –  Jul 20 '18 at 13:24
  • 1
    I am coming to conclusion that you are right: we're saying the same thing. The aspect I am tryingh to emphasize is that the function or method body is not actually executed (as you say "the flow is: going to class' foo() method ... But this is not what's happening." ) The distinction between defining a function and executing a function is often lost on a beginner. In Python, the definition only has to be syntactically correct. The semantic correctness only matters later, when you execute the function. –  Jul 20 '18 at 13:35
  • @scharette Arkadiy is right; being in a class really has nothing to do with it here. The fact that we're looking something up as an attribute of one of the parameters does, but you can do that without creating a class. For example, `def spam(n): return n.bit_length()`. That works if `n` has a `bit_length` method. In the same way, `test.foo()` works if `foo` has a `moo` method. There's really no difference. – abarnert Aug 02 '18 at 08:19