Python's super()

super(type [, objet_ou_type])

retourne la classe de base de type.

Si le second argument est omis, alors l'objet retourné ~n'est pas limité~.

Si le second argument est un objet, alors isinstance(objet, type) doit être vrai.

Si le second argument est un type, alors issubclass(type2, type) doit être vrai.

This is useful for accessing inherited methods that have been overridden in a class.

There are two typical use cases for super. In a class hierarchy with single inheritance, super can be used to refer to parent classes without naming them explicitly, thus making the code more maintainable. This use closely parallels the use of super in other programming languages.

The second use case is to support cooperative multiple inheritance in a dynamic execution environment. This use case is unique to Python and is not found in statically compiled languages or languages that only support single inheritance. This makes it possible to implement “diamond diagrams” where multiple base classes implement the same method. Good design dictates that this method have the same calling signature in every case (because the order of calls is determined at runtime, because that order adapts to changes in the class hierarchy, and because that order can include sibling classes that are unknown prior to runtime).

For both use cases, a typical superclass call looks like this:

class C(B): def method(self, arg): super().method(arg) # This does the same thing as:

                           # super(C, self).method(arg)

Also note that, aside from the zero argument form, super() is not limited to use inside methods. The two argument form specifies the arguments exactly and makes the appropriate references. The zero argument form only works inside a class definition, as the compiler fills in the necessary details to correctly retrieve the class being defined, as well as accessing the current instance for ordinary methods.


In [ ]:
help(super)

Sans super()

Avant l'existance de la fonction super(), we would have hardwired the call with A.bonjour(self).

...


In [ ]:
class A:
    def bonjour(self):
        print("Bonjour de la part de A.")

class B(A):
    def bonjour(self):
        print("Bonjour de la part de B.")
        A.bonjour(self)

b = B()
b.bonjour()

Le même exemple avec un argument:


In [ ]:
class A:
    def bonjour(self, arg):
        print("Bonjour de la part de A. J'ai été appelée avec l'argument arg:", arg)

class B(A):
    def bonjour(self, arg):
        print("Bonjour de la part de B. J'ai été appelée avec l'argument arg:", arg)
        A.bonjour(self, arg)

b = B()
b.bonjour('hey')

Exemple qui montre que A.bonjour() est bien appelée sur l'objet b:


In [ ]:
class A:
    def __init__(self):
        self.nom = "Alice"

    def bonjour(self):
        print("Bonjour de la part de A. Je m'appelle:", self.nom)

class B(A):
    def __init__(self):
        self.nom = "Bob"

    def bonjour(self):
        A.bonjour(self)

b = B()
b.bonjour()

Avec super(): premier exemple

Dans l'exemple précedent, la ligne A.bonjour(self) (dans B.bonjour()) définie explicitement le nom de la classe contenant la fonction à appeler (ici A) ainsi que l'objet (self) sur lequel est appelé la fonction.

Un des deux principaux intérets de super() est de rendre inplicite le nom de la classe d'appel A (ainsi que l'objet self sur lequel est appelé la fonction).

Ainsi, l'appel A.bonjour(self) devient super().bonjour().

Ainsi, par exemple, si on décide de renommer la classe A ou si on décide que B hérite de C plutôt que A, on a pas besoin de mettre à jours le contenu de la fonction B.bonjour(). Les changements sont isolés.


In [ ]:
class A:
    def bonjour(self):
        print("Bonjour de la part de A.")

class B(A):
    def bonjour(self):
        print("Bonjour de la part de B.")
        super().bonjour()   # au lieu de "A.bonjour(self)"

b = B()
b.bonjour()

Le même exemple avec un argument:


In [ ]:
class A:
    def bonjour(self, arg):
        print("Bonjour de la part de A. J'ai été appelée avec l'argument arg:", arg)

class B(A):
    def bonjour(self, arg):
        print("Bonjour de la part de B. J'ai été appelée avec l'argument arg:", arg)
        super().bonjour(arg)

b = B()
b.bonjour('hey')

Exemple qui montre que super().bonjour() est bien appelée sur l'objet b:


In [ ]:
class A:
    def __init__(self):
        self.nom = "Alice"

    def bonjour(self):
        print("Bonjour de la part de A. Je m'appelle:", self.nom)

class B(A):
    def __init__(self):
        self.nom = "Bob"

    def bonjour(self):
        super().bonjour()

b = B()
b.bonjour()

En fait, super() retourne la classe implicite A:


In [ ]:
class A:
    pass

class B(A):
    def bonjour(self):
        print(super())

b = B()
b.bonjour()

Ajout de contraintes: super(type, obj_ou_type) [TODO]

Une autre syntaxe peut être utilisée pour rendre un peut plus explicite la classe de base à utiliser pour l'appel de la fonction:


In [ ]:
class A:
    def bonjour(self):
        print("Bonjour de la part de A.")

class B(A):
    def bonjour(self):
        print("Bonjour de la part de B.")

class C(B):
    def bonjour(self):
        print("Bonjour de la part de C.")
        super(C, self).bonjour()

c = C()
c.bonjour()

Ce qui est l'équivalant de:


In [ ]:
class A:
    def bonjour(self):
        print("Bonjour de la part de A.")

class B(A):
    def bonjour(self):
        print("Bonjour de la part de B.")

class C(B):
    def bonjour(self):
        print("Bonjour de la part de C.")
        super().bonjour()

c = C()
c.bonjour()

In [ ]:
# **TODO**

class A():
    def bonjour(self):
        print("Bonjour de la part de A.")

class B:
    def bonjour(self):
        print("Bonjour de la part de B.")

class C(A, B):
    def bonjour(self):
        print("Bonjour de la part de C.")
        print(super(B, self))
        super(B, self).bonjour()

c = C()
c.bonjour()

L'ordre de résolution des méthodes ("Method Resolution Order" ou MRO en anglais)


In [ ]:
class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

print(D.__mro__)

Les bases d'une classe


In [ ]:
class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

print(D.__bases__)

First use case: ...


In [ ]:

"In addition to isolating changes, there is another major benefit to computed indirection, one that may not be familiar to people coming from static languages. Since the indirection is computed at runtime, we have the freedom to influence the calculation so that the indirection will point to some other class."


In [ ]:
class A(object):
    def hello(self, arg):
        print("Hello", arg, "from A.")

class B(A):
    def hello(self, arg):
        super(B, self).hello(arg)
        print("Hello", arg, "from B.")

a = A()
b = B()

#a.hello('john')
b.hello('john')

In [ ]:
#This works for class methods too:

class C(B):
    @classmethod
    def cmeth(cls, arg):
        super().cmeth(arg)

In [ ]:
class A(object):
    def hello(self, arg):
        print("Hello", arg, "from A.")

class B(A):
    def hello(self, arg):
        super(B, self).hello(arg)
        print("Hello", arg, "from B.")
        
class C(B):
    def hello(self, arg):
        super(C, self).hello(arg)
        print("Hello", arg, "from C.")

a = A()
b = B()
c = C()

c.hello('john')

# comment appeler B.hello() sur c ?
# comment appeler A.hello() sur c ?

In [ ]:
class A(object):
    def hello(self, arg):
        print("Hello", arg, "from A.")

class B(A):
    def hello(self, arg):
        super().hello(arg)
        print("Hello", arg, "from B.")
        
class C(A):
    def hello(self, arg):
        super().hello(arg)
        print("Hello", arg, "from C.")
        
class D(B, C):
    def hello(self, arg):
        super().hello(arg)
        print("Hello", arg, "from D.")

a = A()
b = B()
c = C()
d = D()

a.hello('john')
print()
b.hello('john')
print()
c.hello('john')
print()
d.hello('john')

In [ ]:
class A(object):
    def __init__(self, name):
        self.name = name
        
    def hello(self, arg):
        print("Hello", arg, "from A.")

class B(A):
    def hello(self, arg):
        super().hello(arg)
        print("Hello", arg, "from B.")

a = A("foo")
b = B()

a.hello('john')
print()
b.hello('john')

First use case: ...


In [ ]:
class A:
    def __init__(self):
        self.name = "A"
        
    def hello(self, arg):
        print("Hello from A with arg:", arg, "self beeing", self.name)

class B(A):
    def __init__(self):
        self.name = "B"
        
    def hello(self, arg):
        super().hello(arg)
        print("Hello from B with arg:", arg, "self beeing", self.name)

a = A()
a.hello('foo')

b = B()
b.hello('foo')

In [ ]:
class A:
    def __init__(self, name):
        self.name = name
        
    def hello(self, arg):
        print("Hello from A with arg:", arg, "self beeing", self.name)

class B(A):
    #def __init__(self):
    #    self.name = "B"
        
    def hello(self, arg):
        super().hello(arg)
        print("Hello from B with arg:", arg, "self beeing", self.name)

a = A("A")
a.hello('foo')

b = B()
b.hello('foo')

In [ ]:
class A:
    def __init__(self, arg):
        print(arg)

class B(A):
    pass

b = B()

In [ ]: