What is the difference between:
class Child(SomeBaseClass): def __init__(self): super(Child, self).__init__()
And:
class Child(SomeBaseClass): def __init__(self): SomeBaseClass.__init__(self)
I've seen that only single inherited classes use a lot of super in their classes. I know why you use it in multiple inheritance, but I don't know the benefits of using it in this case.
#1 building
The benefits of super() in single inheritance are trivial - in most cases, you don't have to hardcode the name of the base class into every method that uses its parent method.
However, without super(), it is almost impossible to use multiple inheritance. This includes common idioms such as mixin, interface, abstract class, etc. This extends to code that later extends your code. If someone wants to write classes that extend Child and mixin in the future, their code will not work properly.
#2 building
Don't all of these assume that the base class is a new class?
class A: def __init__(self): print("A.__init__()") class B(A): def __init__(self): print("B.__init__()") super(B, self).__init__()
It doesn't work in Python 2. class A must be a new style, that is: class A(object)
#3 building
Any difference?
SomeBaseClass.__init__(self)
Represents the init of the call to SomeBaseClass. and
super(Child, self).__init__()
Represents the init of the binding called from the parent class of child init in the method resolution order (MRO) of the instance.
If the instance is a Child's subclass, the MRO may be followed by another parent.
Simple explanation
When writing a class, you want other classes to be able to use it. super() makes it easier for other classes to use the class you are writing.
As Bob Martin said, a good architecture allows you to defer decisions as long as possible.
super() enables this architecture.
When another class subclasses a class you write, it may also inherit from other classes. Depending on the order of classes used for method resolution, these classes may have an init after this init.
Without super, you might hardcode the parent of the class you are writing, as shown in the example. This means that you will not invoke the next __init__ in MRO, so you will not be able to reuse the code.
If you are writing your own code for personal use, you may not have to worry about the difference. However, if you want someone else to use your code, it's one thing to use super, which gives code users more flexibility.
Python 2 and 3
This applies to Python 2 and 3:
super(Child, self).__init__()
This only applies to Python 3:
super().__init__()
It takes no parameters, and the method is to move up the stack frame and get the first parameter of the method (usually self for instance methods, cls for class methods, but it can also be another name), and then find the class (such as Child) in the method. Free variable (look up as a free closure variable in the method with the name \.
I prefer to demonstrate the use of super for cross compatibility, but if you only use Python 3, you can call it without any parameters.
Indirect with forward compatibility
What does it give you? For single inheritance, the example of the problem is actually the same from a static analysis point of view. However, using super gives you an indirect layer with forward compatibility.
Forward compatibility is very important for experienced developers. You want the code to keep changes to a minimum. When you look at the revision history, you want to see exactly when the changes were made.
You may start with a single inheritance, but if you decide to add another base class, you only need to change the cardinality row - if the base class changes in the class you inherit (for example, mixin is added), it's OK to change the class. Especially in Python 2, it is difficult to get super parameters and correct method parameters. If you know that you are using super with a single inheritance correctly, it makes debugging easier.
Dependency injection
Others can use your code and inject the parent into method resolution:
class SomeBaseClass(object): def __init__(self): print('SomeBaseClass.__init__(self) called') class UnsuperChild(SomeBaseClass): def __init__(self): print('UnsuperChild.__init__(self) called') SomeBaseClass.__init__(self) class SuperChild(SomeBaseClass): def __init__(self): print('SuperChild.__init__(self) called') super(SuperChild, self).__init__()
Suppose you add another class to the object and want to inject a class between Foo and Bar (for testing or other reasons):
class InjectMe(SomeBaseClass): def __init__(self): print('InjectMe.__init__(self) called') super(InjectMe, self).__init__() class UnsuperInjector(UnsuperChild, InjectMe): pass class SuperInjector(SuperChild, InjectMe): pass
Using un-super sub levels can not inject dependencies, because the subclass you are using already hardcoded the method to be invoked after itself.
>>> o = UnsuperInjector() UnsuperChild.__init__(self) called SomeBaseClass.__init__(self) called
However, classes with Subclasses using super can inject dependencies correctly:
>>> o2 = SuperInjector() SuperChild.__init__(self) called InjectMe.__init__(self) called SomeBaseClass.__init__(self) called
Comment
Why does this work in the world?
Python pass C3 linearization algorithm Complex inheritance tree Linearization To create a method resolution order (MRO).
We want to find methods in that order.
For methods defined in the parent, if you do not use super to find the next method in turn, you must
- Get mro from instance type
- Find the type of definition method
- Use this method to find the next type
- Bind the method and call it with the required parameters
Unsurchild cannot access InjectMe. Why is the conclusion that "always avoid using super"? What do I miss here?
Unsuerchild cannot access injectme. Access to InjectMe is UnsuperInjector, but it is not possible to call the class from its inheritance from UnsuperChild.
Both Child classes intend to call methods with the same name that follows in MRO, which may be another class that it did not know when it was created.
A method whose parent method is not hard coded by super - thus limiting the behavior of its methods, and subclasses cannot inject functionality into the call chain.
With super that has more flexibility. The call chain of these methods can be intercepted and injected into the function.
You may not need this functionality, but subclasses of code may.
conclusion
Always use super to reference the parent class instead of hard coding it.
You intend to refer to the parent of the next line, rather than the parent you see inherited by the child.
Not using super will impose unnecessary restrictions on users of the code.
#4 building
When calling super() to resolve to the class method, instance method or static method of the parent method, we want to pass the current class of its scope as the first parameter to indicate which parent method's scope we want to resolve to and the object of interest as the second parameter to indicate which object we are trying to apply the scope to.
Consider a class hierarchy a, B, and c, where each class is the parent of the next, and a, B, and c are instances of each class, respectively.
super(B, b) # resolves to the scope of B's parent i.e. A # and applies that scope to b, as if b was an instance of A super(C, c) # resolves to the scope of C's parent i.e. B # and applies that scope to c super(B, c) # resolves to the scope of B's parent i.e. A # and applies that scope to c
Use super with static methods
For example, use super() in the new method
class A(object): def __new__(cls, *a, **kw): # ... # whatever you want to specialize or override here # ... return super(A, cls).__new__(cls, *a, **kw)
Explain:
1 - although the reference of the call class is usually the first parameter, it is not implemented as a class method in Python, but as a static method. That is, when you call \
# if you defined this class A(object): def __new__(cls): pass # calling this would raise a TypeError due to the missing argument A.__new__() # whereas this would be fine A.__new__(A)
2 - when the call super() reaches the parent class, we pass subclass A as its first parameter, and then pass A reference to the object of interest, in this case, A. \\\\\\\\\. In most cases, it also happens to be A reference to A subclass. In some cases, such as in the case of multi generation inheritance, this may not be the case.
super(A, cls)
3-because in general, super (a, cls).? new? Will also return static methods, and all parameters need to be explicitly provided, including references to super (a, cls).? new? Objects, in this case, cls.
super(A, cls).__new__(cls, *a, **kw)
4 - do the same thing without super
class A(object): def __new__(cls, *a, **kw): # ... # whatever you want to specialize or override here # ... return object.__new__(cls, *a, **kw)
Use super with instance methods
For example, use super () from init ()
class A(object): def __init__(self, *a, **kw): # ... # you make some changes here # ... super(A, self).__init__(*a, **kw)
Explain:
1 - init is an instance method, which means it takes a reference to the instance as its first parameter. When called directly from an instance, the reference is passed implicitly, that is, you do not need to specify it:
# you try calling `__init__()` from the class without specifying an instance # and a TypeError is raised due to the expected but missing reference A.__init__() # TypeError ... # you create an instance a = A() # you call `__init__()` from that instance and it works a.__init__() # you can also call `__init__()` with the class and explicitly pass the instance A.__init__(a)
2-when super() is called at ﹖ init ﹖, we take the subclass as the first parameter and the object of interest as the second parameter, which is usually a reference to the subclass instance.
super(A, self)
3 - calling super(A, self) returns a proxy that resolves the scope and applies it to self as if it were now an instance of the parent class. Let's call agents. Because init is an instance method, a call to S. init implicitly passes a reference to self as the first argument to the parent's init.
4 - to perform the same operation without super, we need to explicitly pass the reference to the instance to the parent's ﹖ init ﹖ ().
class A(object): def __init__(self, *a, **kw): # ... # you make some changes here # ... object.__init__(self, *a, **kw)
Using super in class methods
class A(object): @classmethod def alternate_constructor(cls, *a, **kw): print "A.alternate_constructor called" return cls(*a, **kw) class B(A): @classmethod def alternate_constructor(cls, *a, **kw): # ... # whatever you want to specialize or override here # ... print "B.alternate_constructor called" return super(B, cls).alternate_constructor(*a, **kw)
Explain:
1- can call the class method directly from the class, and reference the class as its first parameter.
# calling directly from the class is fine, # a reference to the class is passed implicitly a = A.alternate_constructor() b = B.alternate_constructor()
When 2- calls super() into its parent version in a class method, we want to pass the current subclass as the first parameter to indicate which parent range we want to parse, and the object of interest is second parameters indicating which object to apply to the scope, usually referring to one of the subclasses themselves or one of their subclasses.
super(B, cls_or_subcls)
3-call super(B, cls) to resolve to the scope of A and apply it to cls. Since alternate constructor() is A call to A class method super(B, cls). Alternate constructor (...) takes the implicitly passed reference cls as the first parameter and alternates constructor() of version A
super(B, cls).alternate_constructor()
4 - if you do not use super() for the same operation, you need to get the unbound version of a.alternate'constructor() (that is, the explicit version of the function). Simply doing so will not work:
class B(A): @classmethod def alternate_constructor(cls, *a, **kw): # ... # whatever you want to specialize or override here # ... print "B.alternate_constructor called" return A.alternate_constructor(cls, *a, **kw)
The above method doesn't work because the A.alternate'constructor() method implicitly references A as the first parameter. The cls passed here will be its second parameter.
class B(A): @classmethod def alternate_constructor(cls, *a, **kw): # ... # whatever you want to specialize or override here # ... print "B.alternate_constructor called" # first we get a reference to the unbound # `A.alternate_constructor` function unbound_func = A.alternate_constructor.im_func # now we call it and pass our own `cls` as its first argument return unbound_func(cls, *a, **kw)
#5 building
I've played super() and realized that we can change the call order.
For example, we have the next hierarchy:
A / \ B C \ / D
In this case, D's MRO Will be (Python 3 only):
In [26]: D.__mro__ Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)
Let's create a class that calls super() after the method is executed.
In [23]: class A(object): # or with Python 3 can define class A: ...: def __init__(self): ...: print("I'm from A") ...: ...: class B(A): ...: def __init__(self): ...: print("I'm from B") ...: super().__init__() ...: ...: class C(A): ...: def __init__(self): ...: print("I'm from C") ...: super().__init__() ...: ...: class D(B, C): ...: def __init__(self): ...: print("I'm from D") ...: super().__init__() ...: d = D() ...: I'm from D I'm from B I'm from C I'm from A A / ⇖ B ⇒ C ⇖ / D
Therefore, we can see that the resolution order is the same as that in MRO. However, when we call super() at the start of the method:
In [21]: class A(object): # or class A: ...: def __init__(self): ...: print("I'm from A") ...: ...: class B(A): ...: def __init__(self): ...: super().__init__() # or super(B, self).__init_() ...: print("I'm from B") ...: ...: class C(A): ...: def __init__(self): ...: super().__init__() ...: print("I'm from C") ...: ...: class D(B, C): ...: def __init__(self): ...: super().__init__() ...: print("I'm from D") ...: d = D() ...: I'm from A I'm from C I'm from B I'm from D
We have a different order, which is the reverse order of MRO tuples.
A / ⇘ B ⇐ C ⇘ / D
For other readings, I suggest the next answer: