bonne façon d'utiliser super (passage d'argument)

100

J'ai donc suivi Super Considered Harmful de Python et suis allé tester ses exemples.

Cependant, l' exemple 1-3 , qui est censé montrer la manière correcte d'appeler superlors de la gestion de __init__méthodes qui attendent des arguments différents, ne fonctionne pas.

Voici ce que je reçois:

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

Il semble que objectlui - même viole l'une des meilleures pratiques mentionnées dans le document, à savoir que les méthodes qui utilisent superdoivent accepter *argset **kwargs.

Maintenant, évidemment, M. Knight s'attendait à ce que ses exemples fonctionnent, alors est-ce quelque chose qui a été changé dans les versions récentes de Python? J'ai vérifié 2.6 et 2.7, et cela échoue sur les deux.

Alors, quelle est la bonne façon de traiter ce problème?

cha0site
la source
2
Ma méthode préférée est la suivante: Hiérarchies d'héritage plates et simples.
millimoose
19
Vous devriez également lire "Python super () considéré comme super" pour obtenir une vue équilibrée :)
Björn Pollex
@ BjörnPollex: Merci! Votre lien fournit une réponse à la question: Vous pouvez écrire une « classe racine » qui les hérite de object, et il veille à l' appel objectest __init__correctement.
cha0site
5
Notez que __init__sur objectignore silencieusement tous les paramètres de Python 2.5. Cela a changé dans Python 2.6.
Wilfred Hughes
1
@Wilfred: Hé, merci d'avoir répondu à la question! Maintenant, je sais pourquoi l'essai est obsolète!
cha0site

Réponses:

104

Parfois, deux classes peuvent avoir des noms de paramètres en commun. Dans ce cas, vous ne pouvez pas **kwargssupprimer ou supprimer les paires clé-valeur *args. Au lieu de cela, vous pouvez définir une Baseclasse qui, contrairement à object, absorbe / ignore les arguments:

class Base(object):
    def __init__(self, *args, **kwargs): pass

class A(Base):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(Base):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(arg, *args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10)

rendements

MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

Notez que pour que cela fonctionne, Basedoit être l'avant-dernière classe du MRO.

unutbu
la source
4
Ne devrait pas Baseappeler super(Base, self).__init__()?
cha0site
4
Pour que cela fonctionne, il Basefaut arriver à la fin du MRO (sauf pour object). Appeler object.__init__ne fait rien, il est donc normal de ne pas appeler super(Base, self).__init__(). En fait, je pense qu'il serait peut-être plus clair de ne pas inclure super(Base, self).__init__()pour ramener à la maison le point qui Baseest la fin de la ligne.
unutbu
25

Si vous allez avoir beaucoup d'héritage (c'est le cas ici), je vous suggère de passer tous les paramètres en utilisant **kwargs, puis poples juste après les avoir utilisés (sauf si vous en avez besoin dans les classes supérieures).

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

C'est le moyen le plus simple de résoudre ce genre de problèmes.

third = Third(first_arg=1, second_arg=2, third_arg=3)
juliomalegria
la source
6

Comme expliqué dans super () de Python considéré comme super , une façon est de faire en sorte que la classe mange les arguments dont elle a besoin et transmet le reste. Ainsi, lorsque la chaîne d'appels atteint object, tous les arguments ont été mangés et object.__init__seront appelés sans arguments (comme il s'y attend). Donc, votre code devrait ressembler à ceci:

class A(object):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(object):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(*args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(*args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(*args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10, 20, 30)
Björn Pollex
la source
Alors, qu'en est-il E(arg = 10)? Je n'aime pas cette méthode, j'ai besoin de connaître le MRO à l'avance pour pouvoir l'utiliser. L'autre méthode où j'écris une "classe racine" qui hérite de objectsemble beaucoup plus propre.
cha0site
Cette méthode est utile si les fonctions appelées ne partagent pas les noms d'arguments. J'avoue que je n'ai pas beaucoup d'expérience pratique avec super, donc cette approche pourrait en effet être plutôt théorique.
Björn Pollex