web-dev-qa-db-fra.com

Super () est-il cassé en Python-2.x?

On dit souvent que super devrait être évité dans Python 2. J'ai trouvé dans mon utilisation de super in Python 2 qu'il n'agit jamais comme je m'y attend, sauf si je fournis tous les arguments tels que l'exemple:

super(ThisClass, self).some_func(*args, **kwargs)

Il me semble que cela va à l'encontre du but d'utiliser super(), ce n'est ni plus concis, ni bien mieux que TheBaseClass.some_func(self, *args, **kwargs). Dans la plupart des cas, l'ordre de résolution de la méthode est un conte de fées lointain.

41
Matt Joiner

super() n'est pas rompu - il ne faut tout simplement pas le considérer comme la façon standard d'appeler une méthode de la classe de base. Cela n'a pas changé avec Python 3.x. La seule chose qui a changé est que vous n'avez pas besoin de passer les arguments self, cls Dans le cas standard que self est le premier paramètre de la fonction actuelle et cls est la classe en cours de définition.

En ce qui concerne votre question quand utiliser réellement super(), ma réponse serait: presque jamais. J'essaie personnellement d'éviter le type d'héritage multiple qui rendrait super() utile.

Edit: Un exemple de la vie réelle que j'ai rencontré une fois: j'avais quelques classes définissant une méthode run(), dont certaines avaient des classes de base. J'ai utilisé super() pour appeler les constructeurs hérités - je ne pensais pas que cela importait car j'utilisais uniquement l'héritage unique:

class A(object):
    def __init__(self, i):
        self.i = i
    def run(self, value):
        return self.i * value

class B(A):
    def __init__(self, i, j):
        super(B, self).__init__(i)
        self.j = j
    def run(self, value):
        return super(B, self).run(value) + self.j

Imaginez simplement qu'il y avait plusieurs de ces classes, toutes avec des prototypes de constructeurs individuels, et toutes avec la même interface avec run().

Maintenant, je voulais ajouter des fonctionnalités supplémentaires à toutes ces classes, par exemple la journalisation. La fonctionnalité supplémentaire nécessitait la définition d'une méthode supplémentaire sur toutes ces classes, par exemple info(). Je ne voulais pas envahir les classes d'origine, mais plutôt définir un deuxième ensemble de classes héritant des classes originales, ajoutant la méthode info() et héritant d'un mix fournissant la journalisation réelle. Maintenant, je ne pouvais plus utiliser super() dans le constructeur, j'ai donc utilisé des appels directs:

class Logger(object):
    def __init__(self, name):
        self.name = name
    def run_logged(self, value):
        print "Running", self.name, "with info", self.info()
        return self.run(value)

class BLogged(B, Logger):
    def __init__(self, i, j):
        B.__init__(self, i, j)
        Logger.__init__("B")
    def info(self):
        return 42

Ici, les choses cessent de fonctionner. L'appel super() dans le constructeur de la classe de base appelle soudainement Logger.__init__() et BLogged ne peut rien y faire. Il n'y a en fait aucun moyen de faire fonctionner cela, sauf pour supprimer l'appel super() dans B lui-même.

[Another Edit: Je ne semble pas avoir fait mon point, à en juger par tous les commentaires ici et ci-dessous les autres réponses. Voici comment faire fonctionner ce code en utilisant super():

class A(object):
    def __init__(self, i, **kwargs):
        super(A, self).__init__(**kwargs)
        self.i = i
    def run(self, value):
        return self.i * value

class B(A):
    def __init__(self, j, **kwargs):
        super(B, self).__init__(**kwargs)
        self.j = j
    def run(self, value):
        return super(B, self).run(value) + self.j

class Logger(object):
    def __init__(self, name, **kwargs):
        super(Logger,self).__init__(**kwargs)
        self.name = name
    def run_logged(self, value):
        print "Running", self.name, "with info", self.info()
        return self.run(value)

class BLogged(B, Logger):
    def __init__(self, **kwargs):
        super(BLogged, self).__init__(name="B", **kwargs)
    def info(self):
        return 42

b = BLogged(i=3, j=4)

Comparez cela avec l'utilisation d'appels explicites de superclasse. Vous décidez quelle version vous préférez.]

Ceci et des histoires similaires sont pourquoi je pense que super() ne devrait pas être considéré comme la façon standard d'appeler les méthodes de la classe de base . Cela ne signifie pas que super() est rompu.

42
Sven Marnach

super() n'est pas rompu, dans Python 2 ou Python 3.

Examinons les arguments de l'article de blog:

  • Il ne fait pas ce que cela ressemble.

OK, vous pouvez être d'accord ou en désaccord là-dessus, c'est assez subjectif. Comment aurait-il dû s'appeler alors? super() est un remplacement pour appeler directement la superclasse, donc le nom me semble bien. Il n'appelle PAS directement la superclasse, car si c'était tout ce qu'il faisait, ce serait inutile, comme vous pourriez le faire de toute façon. Certes, cela peut ne pas être évident, mais les cas où vous avez besoin de super() ne sont généralement pas évidents. Si vous en avez besoin, vous faites un héritage multiple assez velu. Ça ne va pas être évident. (Ou vous faites un mixage simple, auquel cas il sera assez évident et se comportera comme vous vous attendez même si vous n'avez pas lu les documents).

Si vous pouvez appeler directement la superclasse, c'est probablement ce que vous finirez par faire. C'est la manière simple et intuitive de le faire. super() n'entre en jeu que lorsque cela ne fonctionne pas.

  • Il ne correspond pas bien à l'appel direct de la superclasse.

Oui, car il est conçu pour résoudre un problème avec cela. Vous pouvez appeler la superclasse directement si et seulement si vous savez exactement de quelle classe il s'agit. Ce que vous ne faites pas pour les mixins, par exemple, ou lorsque votre hiérarchie de classe est si foirée que vous fusionnez en fait deux branches (ce qui est l'exemple typique dans tous les exemples d'utilisation de super()).

Donc, tant que chaque classe de votre hiérarchie de classe a une place bien définie, l'appel de la superclasse fonctionne directement. Si ce n'est pas le cas, cela ne fonctionne pas et dans ce cas, vous devez utiliser super() à la place. C'est le point de super() qu'il détermine ce que la "prochaine superclasse" est selon le MRO, sans que vous ayez à le spécifier explicitement, parce que vous ne pouvez pas toujours le faire parce que vous ne savez pas toujours ce que c'est, par exemple lors de l'utilisation de mixins.

  • Le langage de programmation complètement différent Dylan, une sorte de truc LISP, résout cela d'une autre manière qui ne peut pas être utilisé dans Python parce qu'il est très différent.

Eh. D'accord?

  • super() n'appelle pas votre superclasse.

Ouais, tu as dit ça.

  • Ne mélangez pas super() et les appels directs.

Ouais, tu l'as dit aussi.

Donc, il y a deux arguments contre: 1. Le nom est mauvais. 2. Vous devez l'utiliser de manière cohérente.

Cela ne signifie pas qu'il est "cassé" ou qu'il doit être "évité".

31
Lennart Regebro

Vous semblez impliquer dans votre message que

def some_func(self, *args, **kwargs):
    self.__class__.some_func(self, *args, **kwargs)

n'est pas une récursion infinie. C'est, et super serait plus correct.

De plus, oui, vous devez passer tous les arguments à super(). C'est un peu comme se plaindre que max() ne fonctionne pas comme prévu sauf si vous lui passez tous les chiffres que vous voulez vérifier.

Dans 3.x, cependant, moins d'arguments sont nécessaires: vous pouvez faire super().foo(*args, **kwargs) au lieu de super(ThisClass, self).foo(*args, **kwargs).


Quoi qu'il en soit, je ne suis pas sûr des situations où le super devrait être évité. Son comportement n'est "bizarre" que lorsque MI est impliqué, et lorsque MI est impliqué, super() est fondamentalement votre seulement espoir pour une solution correcte. Dans l'héritage simple, il est juste un peu plus verbeux que SuperClass.foo(self, *args, **kwargs), et ne fait rien de différent.

Je pense que je suis d'accord avec Sven que ce type d'IM vaut la peine d'être évité, mais je ne suis pas d'accord que super vaut la peine d'être évité. Si votre classe est censée être héritée, super offre aux utilisateurs de votre classe l'espoir de faire fonctionner MI, s'ils sont bizarres de cette façon, donc cela rend votre classe plus utilisable.

6
Devin Jeanpierre

Avez-vous lu l'article que vous liez? Il ne conclut pas que super doit être évité mais que vous devez vous méfier de ses mises en garde lors de son utilisation. Ces mises en garde sont résumées par l'article, bien que je ne sois pas d'accord avec leurs suggestions.

Le point principal de l'article est que l'héritage multiple peut devenir compliqué, et super n'aide pas autant que l'auteur le voudrait. Cependant, l'héritage multiple sans super est souvent encore plus compliqué.

Si vous ne faites pas d'héritage multiple, super vous donne l'avantage que toute personne héritant de votre classe peut ajouter des mixins simples et leur __init__ serait correctement appelé. N'oubliez pas d'appeler toujours le __init__ de la superclasse, même lorsque vous héritez de object, et pour passer tous les arguments restants (*a et **kw). Lorsque vous appelez d'autres méthodes de la classe parent, utilisez également super, mais cette fois utilisez leur signature appropriée que vous connaissez déjà (c'est-à-dire assurez-vous qu'elles ont la même signature dans toutes les classes).

Si vous faites un héritage multiple, vous devrez creuser plus profondément que cela, et probablement relire le même article plus attentivement pour être conscient des mises en garde. Et ce n'est également que pendant l'héritage multiple lorsque vous pourriez être dans une situation où un appel explicite au parent pourrait être meilleur que super, mais sans scénario spécifique, personne ne peut vous dire si super doit être utilisé ou ne pas.

Le seul changement dans super dans Python 3.x est que vous n'avez pas besoin de passer explicitement la classe actuelle et self à elle. Cela fait super plus attrayant, car son utilisation ne signifierait aucun codage en dur de la classe parente ou de la classe actuelle.

3
Rosh Oxymoron

@Sven Marnach:

Le problème avec votre exemple est que vous mélangez des appels de superclasse explicites B.__init__ Et Logger.__init__ Dans Blogué avec super() dans B. Cela ne fonctionnera pas. Soit vous utilisez tous les appels explicites de superclasse, soit vous utilisez super() sur toutes les classes. Lorsque vous utilisez super() vous devez l'utiliser sur toutes les classes impliquées, y compris A je pense. Dans votre exemple également, je pense que vous pouvez utiliser des appels de superclasse explicites dans toutes les classes, c'est-à-dire utiliser A.__init__ Dans la classe B.

Quand il n'y a pas d'héritage de diamant, je pense que super () n'a pas beaucoup d'avantages. Le problème est, cependant, que vous ne savez pas à l'avance si vous entrerez dans un héritage diamant à l'avenir, dans ce cas, il serait sage d'utiliser super() de toute façon (mais utilisez-le de manière cohérente) . Sinon, vous pourriez devoir changer toutes les classes ultérieurement ou rencontrer des problèmes.

1
Piet van Oostrum