while (1) vs while (True) - Pourquoi y a-t-il une différence (en bytecode python 2)?

115

Intrigué par cette question sur les boucles infinies en perl: while (1) Vs. for (;;) Y a-t-il une différence de vitesse? , J'ai décidé de lancer une comparaison similaire en python. Je m'attendais à ce que le compilateur génère le même code d'octet pour while(True): passet while(1): pass, mais ce n'est en fait pas le cas en python2.7.

Le script suivant:

import dis

def while_one():
    while 1:
        pass

def while_true():
    while True:
        pass

print("while 1")
print("----------------------------")
dis.dis(while_one)

print("while True")
print("----------------------------")
dis.dis(while_true)

produit les résultats suivants:

while 1
----------------------------
  4           0 SETUP_LOOP               3 (to 6)

  5     >>    3 JUMP_ABSOLUTE            3
        >>    6 LOAD_CONST               0 (None)
              9 RETURN_VALUE        
while True
----------------------------
  8           0 SETUP_LOOP              12 (to 15)
        >>    3 LOAD_GLOBAL              0 (True)
              6 JUMP_IF_FALSE            4 (to 13)
              9 POP_TOP             

  9          10 JUMP_ABSOLUTE            3
        >>   13 POP_TOP             
             14 POP_BLOCK           
        >>   15 LOAD_CONST               0 (None)
             18 RETURN_VALUE        

L'utilisation while Trueest nettement plus compliquée. Pourquoi est-ce?

Dans d'autres contextes, python agit comme s'il était Trueégal à 1:

>>> True == 1
True

>>> True + True
2

Pourquoi whiledistingue les deux?

J'ai remarqué que python3 évalue les instructions en utilisant des opérations identiques:

while 1
----------------------------
  4           0 SETUP_LOOP               3 (to 6) 

  5     >>    3 JUMP_ABSOLUTE            3 
        >>    6 LOAD_CONST               0 (None) 
              9 RETURN_VALUE         
while True
----------------------------
  8           0 SETUP_LOOP               3 (to 6) 

  9     >>    3 JUMP_ABSOLUTE            3 
        >>    6 LOAD_CONST               0 (None) 
              9 RETURN_VALUE         

Y a-t-il un changement dans python3 dans la façon dont les booléens sont évalués?

AndrewF
la source

Réponses:

124

En Python 2.x, il Truene s'agit pas d'un mot-clé, mais simplement d'une constante globale intégrée définie à 1 dans le booltype. Par conséquent, l'interpréteur doit toujours charger le contenu de True. En d'autres termes, Trueest réaffectable:

Python 2.7 (r27:82508, Jul  3 2010, 21:12:11) 
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> True = 4
>>> True
4

En Python 3.x, cela devient vraiment un mot - clé et une vraie constante:

Python 3.1.2 (r312:79147, Jul 19 2010, 21:03:37) 
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> True = 4
  File "<stdin>", line 1
SyntaxError: assignment to keyword

ainsi l'interpréteur peut remplacer la while True:boucle par une boucle infinie.

KennyTM
la source
1
@MH: AFAIK, c'était un expédient pour mettre le mot-clé dans la langue.
S.Lott
2
Voir docs.python.org/whatsnew/2.3.html#pep-285-a-boolean-type et python.org/dev/peps/pep-0285 pour une partie de l'historique.
Ned Deily
Est-ce que cela signifie while 1et while Trueest identique dans Python 3?
Stevoisiak
@ StevenM.Vascellaro Oui.
kennytm
14

Ce n'est pas tout à fait correct,

ainsi l'interpréteur peut remplacer la boucle while True: par une boucle infinie.

car on peut encore sortir de la boucle. Mais il est vrai qu'une telle elseclause de boucle ne serait jamais accessible en Python 3. Et il est également vrai qu'en simplifiant la recherche de valeur, elle s'exécute aussi rapidement que while 1dans Python 2.

Comparaison des performances

Démonstration de la différence de temps pour une boucle while quelque peu non triviale:

Installer

def while1():
    x = 0
    while 1:
        x += 1
        if x == 10:
            break

def whileTrue():
    x = 0
    while True:
        x += 1
        if x == 10:
            break

Python 2

>>> import timeit
>>> min(timeit.repeat(while1))
0.49712109565734863
>>> min(timeit.repeat(whileTrue))
0.756627082824707

Python 3

>>> import timeit
>>> min(timeit.repeat(while1))
0.6462970309949014
>>> min(timeit.repeat(whileTrue))
0.6450748789939098

Explication

Pour expliquer la différence, en Python 2:

>>> import keyword
>>> 'True' in keyword.kwlist
False

mais en Python 3:

>>> import keyword
>>> 'True' in keyword.kwlist
True
>>> True = 'true?'
  File "<stdin>", line 1
SyntaxError: can't assign to keyword

Puisqu'il Trues'agit d'un mot-clé dans Python 3, l'interpréteur n'a pas besoin de rechercher la valeur pour voir si quelqu'un l'a remplacée par une autre valeur. Mais comme on peut attribuer Trueà une autre valeur, l'interprète doit la rechercher à chaque fois.

Conclusion pour Python 2

Si vous avez une boucle serrée et longue dans Python 2, vous devriez probablement utiliser à la while 1:place de while True:.

Conclusion pour Python 3

À utiliser while True:si vous n'avez aucune condition pour sortir de votre boucle.

Salle Aaron
la source
3

Il s'agit d'une question vieille de 7 ans qui a déjà une excellente réponse, mais une idée fausse dans la question, qui n'est abordée dans aucune des réponses, la rend potentiellement déroutante pour certaines des autres questions marquées comme des doublons.

Dans d'autres contextes, python agit comme si True était égal à 1:

>>> True == 1
True

>>> True + True
2

Pourquoi tout distingue-t-il les deux?

En fait, whilerien de différent ici du tout. Il distingue 1et Trueexactement de la même manière que l' +exemple.


Voici 2.7:

>>> dis.dis('True == 1')
  1           0 LOAD_GLOBAL              0 (True)
              3 LOAD_CONST               1 (1)
              6 COMPARE_OP               2 (==)
              9 RETURN_VALUE

>>> dis.dis('True == 1')
  1           0 LOAD_GLOBAL              0 (True)
              3 LOAD_GLOBAL              0 (True)
              6 BINARY_ADD
              9 RETURN_VALUE

Maintenant, comparez:

>>> dis.dis('1 + 1')
  1           0 LOAD_CONST               1 (2)
              3 RETURN_VALUE

Il émet un LOAD_GLOBAL (True)pour chacun True, et l'optimiseur ne peut rien faire avec un global. Donc, whiledistingue 1et Truepour exactement la même raison qui le +fait. (Et ==ne les distingue pas car l'optimiseur n'optimise pas les comparaisons.)


Maintenant, comparez 3.6:

>>> dis.dis('True == 1')
  1           0 LOAD_CONST               0 (True)
              2 LOAD_CONST               1 (1)
              4 COMPARE_OP               2 (==)
              6 RETURN_VALUE

>>> dis.dis('True + True')
  1           0 LOAD_CONST               1 (2)
              2 RETURN_VALUE

Ici, il émet un LOAD_CONST (True)pour le mot-clé, dont l'optimiseur peut tirer parti. Donc, True + 1 ne fait pas de distinction, pour exactement la même raison while True. (Et ==ne les distingue toujours pas car l'optimiseur n'optimise pas les comparaisons.)


Pendant ce temps, si le code n'est pas optimisé, l'interpréteur finit par traiter Trueet 1exactement la même chose dans ces trois cas. boolest une sous-classe de int, et hérite de la plupart de ses méthodes de int, et Truea une valeur entière interne de 1. Donc, que vous fassiez un whiletest ( __bool__dans 3.x, __nonzero__dans 2.x), une comparaison ( __eq__) ou de l'arithmétique ( __add__), vous appelez la même méthode que vous utilisiez Trueou 1.

Abarnert
la source