Python optimise-t-il une variable qui n'est utilisée que comme valeur de retour?

106

Existe-t-il une différence ultime entre les deux extraits de code suivants? Le premier attribue une valeur à une variable dans une fonction, puis renvoie cette variable. La deuxième fonction renvoie simplement la valeur directement.

Python les transforme-t-il en bytecode équivalent? L'un d'eux est-il plus rapide?

Cas 1 :

def func():
    a = 42
    return a

Cas 2 :

def func():
    return 42
Jayesh
la source
5
Si vous utilisez dis.dis(..)les deux, vous voyez qu'il y a une différence , donc oui. Mais dans la plupart des applications du monde réel , la surcharge de ceci par rapport au retard du traitement dans la fonction n'est pas si grande.
Willem Van Onsem
4
Il y a deux possibilités: (a) Vous allez appeler cette fonction plusieurs fois (c'est-à-dire au moins un million) dans une boucle serrée. Dans ce cas, vous ne devriez pas du tout appeler une fonction Python, mais plutôt vectoriser votre boucle en utilisant quelque chose comme la bibliothèque numpy. (b) Vous n'allez pas appeler cette fonction autant de fois. Dans ce cas, la différence de vitesse entre ces fonctions est trop petite pour qu'il vaille la peine de s'inquiéter.
Arthur Tacca

Réponses:

138

Non, ce n'est pas le cas .

La compilation en code octet CPython est uniquement transmise à travers un petit optimiseur de judas conçu pour ne faire que des optimisations de base (voir test_peepholer.py dans la suite de tests pour plus d'informations sur ces optimisations).

Pour voir ce qui va réellement se passer, utilisez dis* pour voir les instructions générées. Pour la première fonction, contenant l'affectation:

from dis import dis
dis(func)
  2           0 LOAD_CONST               1 (42)
              2 STORE_FAST               0 (a)

  3           4 LOAD_FAST                0 (a)
              6 RETURN_VALUE

Alors que, pour la deuxième fonction:

dis(func2)
  2           0 LOAD_CONST               1 (42)
              2 RETURN_VALUE

Deux autres instructions (rapides) sont utilisées dans la première: STORE_FASTet LOAD_FAST. Celles-ci effectuent un stockage et une saisie rapides de la valeur dans le fastlocalstableau de la trame d'exécution actuelle. Ensuite, dans les deux cas, un RETURN_VALUEest effectué. Ainsi, la seconde est un peu plus rapide en raison de moins de commandes nécessaires à l'exécution.

En général, sachez que le compilateur CPython est prudent dans les optimisations qu'il effectue. Il n'est pas et n'essaye pas d'être aussi intelligent que les autres compilateurs (qui, en général, ont également beaucoup plus d'informations sur lesquelles travailler). L'objectif principal de la conception, en plus d'être évidemment correct, est de a) rester simple et b) être aussi rapide que possible dans la compilation de ces derniers afin que vous ne remarquiez même pas qu'une phase de compilation existe.

En fin de compte, vous ne devriez pas vous soucier de petits problèmes comme celui-ci. L'avantage en vitesse est minime, constant et, éclipsé par la surcharge introduite par le fait que Python est interprété.

* disest un petit module Python qui désassemble votre code, vous pouvez l'utiliser pour voir le bytecode Python que la VM exécutera.

Remarque: comme indiqué également dans un commentaire de @Jorn Vernee, ceci est spécifique à l'implémentation CPython de Python. D'autres implémentations pourraient faire des optimisations plus agressives si elles le souhaitent, CPython ne le fait pas.

Dimitris Fasarakis Hilliard
la source
11
Pas une personne python (c ++) donc je ne sais pas comment cela fonctionne sous le capot mais le premier cas ne devrait-il pas être optimisé pour le second cas? Un compilateur C ++ décent ferait cette optimisation.
NathanOliver
7
@NathanOliver ce n'est vraiment pas le cas, Python fera ce qui est dit ici sans même essayer de le jouer intelligemment.
Dimitris Fasarakis Hilliard
80
Le fait que la réponse parfaitement raisonnable et intelligente de @ NathanOliver à une réponse à cette question soit complètement fausse est, à mes yeux, la preuve qu'il ne s'agit pas d'une question "explicite", "absurde", "stupide" à laquelle on peut répondre en "prenant un moment pour réfléchir", comme TigerhawkT3 voudrait nous le faire croire. C'est une question valable et intéressante à laquelle je n'étais pas certain de la réponse malgré le fait d'avoir été programmeur Python professionnel pendant des années.
Mark Amery
Le compilateur de Python est au mieux «conservateur», pas «très conservateur». L'objectif principal de la conception n'est pas d'être "aussi rapide que possible ... vous ne remarquerez même pas qu'une phase de compilation existe". C'est secondaire, après "Keep it simple". Une fonction avec de grandes constantes comme "1 << (2 ** 34)" et "b'x '* (2 ** 32)" prend plusieurs secondes à compiler et génère des constantes de taille Go, même si la fonction n'est jamais courir. La grande chaîne sera même supprimée par le compilateur. Les correctifs proposés pour ces cas ont été rejetés car ils rendraient le compilateur trop complexe.
Andrew Dalke
@AndrewDalke merci pour les commentaires des initiés à ce sujet, j'ai modifié le libellé pour répondre aux problèmes que vous avez signalés.
Dimitris Fasarakis Hilliard
3

Les deux sont fondamentalement les mêmes sauf que dans le premier cas l'objet 42est simplement assigné à une variable nommée aou, en d'autres termes, les noms (ie a) font référence à des valeurs (ie 42). Il ne fait aucune affectation techniquement, dans le sens où il ne copie jamais aucune donnée.

Lors de l' returning, cette liaison nommée aest retournée dans le premier cas tandis que l'objet 42est renvoyé dans le second cas.

Pour plus de lecture, reportez - vous à cet excellent article de Ned Batchelder

kmario23
la source