I wonder how is class C being executed in this code flow
When none of the flow calls it.
class A:
def fun1(self):
print("This is class A")
class B(A):
def fun2(self):
super().fun1()
print("This is class B")
class C(A):
def fun1(self):
super().fun1()
print("This is class C")
class D(B, C):
def fun1(self):
self.fun2()
print("This is class D")
obj = D()
obj.fun1()
"""
# Result of this code is
This is class A
This is class C
This is class B
This is class D
"""
The method resolution order used is a C3 linearization algorithm, and super itself uses this MRO. The resolution for a type is obtained with the mro() method, and cached in the __mro__ attribute:
>>> D.__mro__
(__main__.D, __main__.B, __main__.C, __main__.A, object)
>>> D.mro()
[__main__.D, __main__.B, __main__.C, __main__.A, object]
Since you call print after calling super, you'll see the reverse of this, i.e. A -> C -> B -> D.
I wonder how is class C being executed in this code flow when none of the flow calls it.
D.fun2 doesn't exist, so obj.fun2() gets resolved (via the MRO) on B instead:
>>> obj = D()
>>> obj.fun2
<bound method B.fun2 of <__main__.D object at 0x7fffe89a3cd0>>
Then in B.fun2, the C.fun1 gets called here:
class B(A):
def fun2(self):
super().fun1() # <-- this resolves to C.fun1, not A.fun1!
print("This is class B")
C.fun1 is called since it's D's MRO which is active here, not B's, because the type(self) is D. Note that super() does not always resolve on the parent class, it can be resolved on a sibling class like in your example. Python's implementation was more or less lifted from another programming language called Dylan, where super was named next-method, a less confusing name in my opinion.
Related
In the book, Python in a Nutshell,
the authors claim the following code snippet is problematic:
class B:
def f(self):
return 23
g = property(f)
class C(B):
def f(self):
return 42
c = C()
print(c.g) # prints: 23, not 42
And the solution, as the authors claimed, is to redirect the calculation of property c.g to the class C's implementation of f at the superclass B's level.
class B:
def f(self):
return 23
def _f_getter(self):
return self.f()
g = property(_f_getter)
class C(B):
def f(self):
return 42
c = C()
print(c.g) # prints: 42, as expected
I disagree but please help me to understand why the authors claimed as they did.
Two reasons for my disagreement.
Superclasses are far likely to be in upstream modules and it is not ideal for upstream modules to implement additional indirection to take into account that subclasses in downstream modules may reimplement the getter method for the superclass property.
The calculation of a property of a superclass is done after the instantiation of an instance of the superclass. An instance of the subclass must be instantiated after the instantiation of an instance of the superclass. Therefore, if the writer of C does not explicitly "declare" C.g to be a property with a different implementation C.f, then it should rightly inherits the property B.g and i.e. c.g should be b.g.
My question is:
am I right with this thought or are the authors right with their claims?
This is a slight more in depth explanation of what is happening.
Basically in your first snippet (which is not very idiomatic python), you bind the exising B.f method to B.g, when C inherits g, its still bound to B.f. g is basically statically linked to B.f.
class B:
def f(self):
return 23
g = property(f) # B.f gets bound to B.g as property
class C(B):
def f(self):
return 42
# C.g not redeclared is still bound to B.f
The "correction snippet" is even less idiomatic, and bind a method which will retrieve the f method from self, the instance it is run on, which makes it dynamic because if self's f method is different from B.f, then the result changes. However we still bind statically to a private method.
class B:
def f(self):
return 23
def _f_getter(self):
return self.f()
g = property(_f_getter) # B._f_getter gets bound to B.g, but B._f_getter uses the f method of self
class C(B):
def f(self):
return 42
# C.g is still bound to B._f_getter but the f method of self has changed
This is the idiomatic way of using property, as a decorator around another method. We can use the same principle as above, but rather than declaring a private intermediate method, we can use g as a name directly.
class B:
def f(self):
return 23
#property
def g(self): # no binding the B.g property uses f method of self directly
return self.f()
class C(B):
def f(self):
return 42
# C.g uses f method of self which has changed
"The calculation of a property of a superclass is done after the instantiation of an instance of the superclass. " - NO, the property is declared along with a class declaration and it's tied to a target function in that class.
print(vars(B)['g']) # <property object at 0x7f5f799ebce0>
"An instance of the subclass must be instantiated after the instantiation of an instance of the superclass." - NO again. That's obvious. Declaration and instantiation are different processes.
"Therefore, if the writer of C does not explicitly "declare" C.g to be a property with a different implementation C.f, then it should rightly inherits the property B.g" - It inherits g property object in its state on declaration phase (i.e. tied to B.f function).
So, you naturally have valid options:
to override a property in your subclass
or to declare your initial property object to consider inheritance, which is no bad
class B:
def f(self):
return 23
g = property(lambda self: self.f())
class C(B):
def f(self):
return 42
c = C()
print(c.g) # 42
I don't know about property but why don't you use:
class B:
def f(self):
return 23
#property
def g(self):
return self.f()
class C(B):
def f(self):
return 42
c = C()
print(c.g)
print(type(c.g))
# Output
42
int
It's not the same as your initial code?
This question already has answers here:
How does Python's "super" do the right thing?
(5 answers)
Closed 3 years ago.
I am looking at the diamond problem and got a question:
class A:
def __init__(self):
print("This is class A")
class B(A):
def __init__(self):
print("This is class B")
super().__init__()
class C(A):
def __init__(self):
print("This is class C")
super().__init__()
class D(B, C):
def __init__(self):
print("This is class D")
super().__init__()
i = D()
This is class D
This is class B
This is class C
This is class A
It works as intended and that's nice, but i would like to know why the super().__init__() in class B doesn't go to class A and instead C is called.
If a class has a super() and it inherits from a parent class, it should go there.
If i remove it on B the code won't get to C nor A.
I know of the MRO and how it is actually going as expected:
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
But i don't know why.
It's very weird than the non-super() implementation of this code has the same MRO yet A is printed twice:
class A:
def __init__(self):
print("This is class A")
class B(A):
def __init__(self):
print("This is class B")
A.__init__(self)
class C(A):
def __init__(self):
print("This is class C")
A.__init__(self)
class D(B, C):
def __init__(self):
print("This is class D")
B.__init__(self)
C.__init__(self)
i = D()
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
Here is the opposite, i know the MRO is correct, but it's weird the actual execution doesn't go that way:
This is class D
This is class B
This is class A
This is class C
This is class A
I would like to know what is the logic behind super() behavior.
When asking this around the web pretty much everyone links me to this: https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ but i really don't get it, his explanation seems way too technical and his examples (the few ones i understood) far more complex than they should be to explain the point...which is why i would like a...simpler explanation.
Super() has to follow the MRO even if the inheritance on the parent class would suggest otherwise?
Super() is unable to go to the parent class of a parent class and therefore if there is a super in a parent class it will go instead to the second inherited class?
Also, kind of unrelated but how common is to see the diamond problem in a real, workplace enviroment? Seems like a very convoluted way to work.
You need to keep in mind that the MRO is not just a simple follow-the-leader. It creates a graph-like structure and resolves identical inheritances to avoid duplication. In your first example, you have created a diamond inheritance.
A
/ \
B C
\ /
D
The MRO seeks resolution of methods from the first level up (the immediate parent classes), from left to right (B then C), then the next level up, from left to right (just A here), and so on.
In this case, because you have both B and C inheriting from A, the top level resolves to a single A and creates the diamond diagram above.
Let's look at your second example:
class D(B, C):
def __init__(self):
print("This is class D")
B.__init__(self)
C.__init__(self)
By implementing it this way, you are effectively by-passing the MRO. You have taken the inheritance diamond and made it an inheritance olive fork that looks like this:
A A
| |
B C
\ /
D
Because of this you are calling the initialization of A twice, which does not need to occur. In long inheritance chains or complicated initialization routines, this is very inefficient.
class A(object):
def foo(self):
print 'hi'
class B(A):
def foo(self):
print 'bye'
Which of these statements are correct?
When a = A() we say that a is an instance of A
When b = B() we say that b is a subclass of A
Both of the above
Neither of the above
I believe that the answer is B.
class B inherits class A.
So class B is a subclass of A.
But when you instantiate A, a = A(), a is indeed an instance of A.
Now, I'm not sure about the wording, since, b = B(), is an instance of B... which is a subclass of A... but an instance is not a class or subclass.
It is instead an instance of A... since B is child of A through inheritance.
So in summary, when you instantiate an object, it is a instance of the class and all of that classes parents. But an object is not a class.
In python, a class instance can be used almost like a function, if it has a __call__ method. I want to have a class B that has a method A, where A is the instance of a class with a __call__ method. For comparison, I also define two other methods foo and bar in the "traditional" way (i.e. using def). Here is the code:
class A(object):
def __call__(*args):
print args
def foo(*args):
print args
class B(object):
A = A()
foo = foo
def bar(*args):
print args
When I call any of the methods of B, they are passed a class instance as implicit first argument (which is conventionally called self).
Yet, I was surprised to find that b.A() gets passed an A instance, where I would have expected a B instance.
In [13]: b = B()
In [14]: b.A()
(<__main__.A object at 0xb6251e0c>,)
In [15]: b.foo()
(<__main__.B object at 0xb6251c0c>,)
In [16]: b.bar()
(<__main__.B object at 0xb6251c0c>,)
Is there a way (maybe a functools trick or similar) to bind A() in such a way that b.A() is passed a reference to the b object?
Please note that the example presented above is simplified for the purpose of illustrating my question. I'm not asking for advice on my actual implementation use case, which is different.
Edit: I get the same output, if I define my class B like this:
class B(object):
def __init__(self):
self.A = A()
foo = foo
def bar(*args):
print args
The problem with your code is:
class B(object):
A = A()
class B has a member named A that is an instance of A. When you do B.A(), it calls the method __call__ of that A instance (that is confusingly named A); and since it is an A all the time, and A's method, of course the actual object in args is an A.
What you're after is a descriptor; that is, A should have the magic method __get__; :
class A(object):
def __get__(self, cls, instance):
print(cls, instance)
return self
class B(object):
a = A()
b = B()
c = b.a
Now when b.a is executed, __get__ method gets B and b as the cls and instance arguments, and whatever it returns is the value from the attribute lookup (the value that is stored in c) - it could return another instance, or even a function, or throw an AttributeError - up to you. To have another function that knows the B and b; you can do:
class A(object):
def __get__(self, cls, instance):
if instance is not None:
def wrapper():
print("I was called with", cls, "and", instance)
return wrapper
return self
class B(object):
a = A()
B.a()
The code outputs:
I was called with <__main__.B object at 0x7f5d52a7b8> and <class '__main__.B'>
Task accomplished.
In Python 2.7, I am trying to reconstruct AN inheritance chain from a certain class E to the root A. There is a diamond inheritance problem as shown below, but I am interested in a path, not THE path, so it should work. Whether it's something I should be doing (this way) is questionable, but right now I just want to know what I am misunderstanding...
class A(object):
#classmethod
def supah(thisCls):
return [cls for cls in thisCls.__bases__ if issubclass(cls, A)]
def name(self):
return 'A'
class C(A):
def name(self):
return 'C'
class D(A):
def name(self):
return 'D'
class E(C, D):
def name(self):
return 'E'
current = E
while True:
try:
print current, super(current, E()), super(current, E()).name()
except AttributeError:
break
current = current.supah()[0]
The output
<class '__main__.E'> <super: <class 'E'>, <E object>> C
<class '__main__.C'> <super: <class 'C'>, <E object>> D
<class '__main__.A'> <super: <class 'A'>, <E object>>
What is D doing there? It is calling
super(C, E()).name()
where super(C, E()) should be "class A", right? If the C on the first line had been an D I would have (sort of) understood, but in my mind the second line should definitely be an A.
Any help?
EDIT: My understanding was that calling
super(C, obj).name()
would result in the name "A", because the linearization of C is [C, A, object].
However, this is not what super(C, obj).name() means apparently. It still uses the full linearization of obj: [E, C, D, A, object] (thanks to #Martijn Pieters), it just starts at (after) C. Therefore D comes before A.
super() doesn't look at __bases__; it looks at the Method Resolution Order (MRO), through type(self).mro():
>>> E.mro()
[<class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <type 'object'>]
As you can see, D is in there, because it is a base class of E; when you call super(C, E()).name(), D comes next in the MRO.
The MRO will always include all base classes in a hierarchy; you cannot build a class hierarchy where the MRO could not be established. This to prevent classes being skipped in a diamond inheritance pattern.
How the MRO works is explained in detail in The Python 2.3 Method Resolution Order.
You may also want to read Guido van Rossum's explanation; he uses a diamond pattern with:
class A:
def save(self): pass
class B(A): pass
class C(A):
def save(self): pass
class D(B, C): pass
to illustrate why an MRO is important; when calling D().save() you'd want C.save() to be invoked (more specialized), not A.save().
If you really wanted to skip D from C.name, you'd have to explicitly find C.__bases__[0] in the MRO, then tell super() to start search for the next .name() method from there:
mro = type(self).mro()
preceding = mro[0]
for i, cls in enumerate(mro[1:], 1):
if cls in self.__bases__:
preceding = mro[i - 1]
name = super(preceding, self).name()
For your E.mro() and class C, this'll find D, as it precedes the first base class of C, A. Calling super(D, self).name() then tells super() to find the first class past D with a name() method, which is A here.
The answer by #Martijn Pieters explains how the observed result is produced.
In case one wants to produce the result that I was incorrectly expecting from super, one might use an approach based on the accepted answer from #Sven Marnach on python: super()-like proxy object that starts the MRO search at a specified class
If you want to get something that behalves as a class A version of a C instance:
class Delegate:
def __init__(self, cls, obj):
self._delegate_cls = cls
self._delegate_obj = obj
def __getattr__(self, name):
x = getattr(self._delegate_cls, name)
if hasattr(x, "__get__"):
return x.__get__(self._delegate_obj)
return x
which can be used to get .name() from A like this:
class C(A):
def name(self):
return delegate(A, self).name() + 'C'
C().name()
# result: AC
If you are interested in a super-like construct that gets a (the first) direct ancestor:
class parent:
def __init__(self, cls, obj):
if len(cls.__bases__):
self._delegate_cls = cls.__bases__[0]
else:
raise Exception('parent called for class "%s", which has no base classes')
self._delegate_obj = obj
def __getattr__(self, name):
x = getattr(self._delegate_cls, name)
if hasattr(x, '__get__'):
return x.__get__(self._delegate_obj)
return x
called like this:
class C(A):
def name(self):
return parent(C, self).name() + 'C'
print C().name()
# result: AC
I don't think there is a way to not explicitly include the name of the current class, just like super (in py2).
Note that this is for special cases. E.g. in my example if C doesn't implement .name() it calls the one on A, never D. It does however let you get a (not 'the') direct ancestor line from a class to the root. Also, parent(Cls, obj) will always be a parent of obj, not a class that Cls known nothing about but which happens to be an ancestor of obj.