2

Could you please help me to understand the difference between these two cases?

class B1: def f(self): super().temp() class B2: def temp(self): print("B2") class A(B1, B2): pass A().f() 

It prints "B2".

If we switch B1 and B2:

class A(B2, B1): pass A().f() 

I get AttributeError: 'super' object has no attribute 'temp'

2
  • 3
    What is confusing you? B2 doesn't implement f, so if it's looked up there first (in the second case) it can't be found. Commented Nov 2, 2014 at 17:57
  • I guess the OP doesn't understand why the order of the arguments to class A matters here, or what effect the ordering has. Commented Nov 2, 2014 at 18:03

2 Answers 2

2

Python uses something called C3 linearization to decide what order the base classes are in: the "method resolution order". This has basically two parts when stated informally:

  • The path must hierarchy must never go down from a class to its superclass, even indirectly. As such, no cycles are allowed and issubclass(X, Y) and issubclass(Y, Z) implies issubclass(X, Z).

  • The order when not forced by the rule above is ordered by number of steps to the super-most class (lower number of steps means earlier in the chain) and then the order of the classes in the class lists (earlier in the list means earlier in the chain).

The hierarchy here is:

 A / \ / \ B1 B2 # Possibly switched \ / \ / object 

In the first case the order after C3 linearization is

 super super super A → B1 → B2 → object 

which we can find out with:

A.mro() #>>> [<class 'A'>, <class 'B1'>, <class 'B2'>, <class 'object'>] 

So the super() calls would resolve as:

class A(B1, B2): pass class B1: def f(self): # super() proxies the next link in the chain, # which is B2. It implicitly passes self along. B2.temp(self) class B2: def temp(self): print("B2") 

so calling A().f() tries:

  • Is f on the instance? No, so
  • Is f on the first class, A? No, so
  • Is f on the next class, B1? Yes!

Then B1.f is called and this calls B2.temp(self), which checks:

  • Is f on the class, B2? Yes!

And it is called, printing B2

In the second case we have

 super super super A → B2 → B1 → object 

So the resolves

So the super() calls would resolve as:

class A(B2, B2): pass class B2: def temp(self): print("B2") class B1: def f(self): # super() proxies the next link in the chain, # which is B2. It implicitly passes self along. object.temp(self) 
  • Is f on the instance? No, so
  • Is f on the first class, A? No, so
  • Is f on the next class, B2? No, so
  • Is f on the next class, B1? Yes!

So B1.f is called and this calls object.temp(self), which checks:

  • Is f on the class, object? No,
  • There are no superclasses, so we have failed to find the attribute.
  • Raise AttributeError("{!r} object has no attribute {!r}".format(instance, attribute_name)).
Sign up to request clarification or add additional context in comments.

Comments

0

The difference is simply the order of classes in MRO of class A in both cases:

class A1(B1, B2): pass class A2(B2, B1): pass print(A1.mro()) print(A2.mro()) 

Which returns:

[<class '__main__.A1'>, <class '__main__.B1'>, <class '__main__.B2'>, <class 'object'>] 

and

[<class '__main__.A2'>, <class '__main__.B2'>, <class '__main__.B1'>, <class 'object'>] 

Now when you call A1.f() or A2.F() the attribute is found in B1, and there you call super().temp(), which means call temp() on the next class found(or move on to class next to it not until temp is not found and so on..) in MRO.

As the next and only class in case of A2 is object which has no temp() method an error is raised.

In case of A1 next class after B1 is B2 which has a temp() method, hence no error is raised.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.