while (1) vs. while (Verdadero) - ¿Por qué hay una diferencia (en el código de bytes de Python 2)?

115

Intrigado por esta pregunta sobre bucles infinitos en perl: while (1) Vs. para (;;) ¿Hay una diferencia de velocidad? , Decidí ejecutar una comparación similar en Python. Esperaba que el compilador generara el mismo código de bytes para while(True): passy while(1): pass, pero este no es el caso en python2.7.

El siguiente guión:

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)

produce los siguientes resultados:

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        

Usar while Truees notablemente más complicado. ¿Por qué es esto?

En otros contextos, Python actúa como si fuera Trueigual a 1:

>>> True == 1
True

>>> True + True
2

¿Por qué whiledistingue a los dos?

Noté que python3 evalúa las declaraciones utilizando operaciones idénticas:

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         

¿Hay un cambio en python3 en la forma en que se evalúan los booleanos?

AndrewF
fuente

Respuestas:

124

En Python 2.x, Trueno es una palabra clave, sino solo una constante global incorporada que se define como 1 en el booltipo. Por lo tanto, el intérprete todavía tiene que cargar el contenido de True. En otras palabras, Truees reasignable:

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 realmente se convierte en una palabra clave y una constante real:

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

así, el intérprete puede reemplazar el while True:bucle con un bucle infinito.

Kennytm
fuente
1
@MH: AFAIK, fue un expediente llevar la palabra clave al idioma.
S.Lott
2
Consulte docs.python.org/whatsnew/2.3.html#pep-285-a-boolean-type y python.org/dev/peps/pep-0285 para conocer parte de la historia.
Ned Deily
¿Significa esto while 1y while Trueson idénticos en Python 3?
Stevoisiak
@ StevenM.Vascellaro Sí.
kennytm
14

Esto no está del todo bien

así, el intérprete puede reemplazar el ciclo while True: con un ciclo infinito.

ya que uno todavía puede salir del bucle. Pero es cierto que elsenunca se accedería a dicha cláusula de bucle en Python 3. Y también es cierto que simplificar la búsqueda de valores hace que se ejecute tan rápido como while 1en Python 2.

Comparación de rendimiento

Demostrando la diferencia en el tiempo para un bucle while algo no trivial:

Preparar

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

Explicación

Para explicar la diferencia, en Python 2:

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

pero en Python 3:

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

Dado que Truees una palabra clave en Python 3, el intérprete no tiene que buscar el valor para ver si alguien lo reemplazó con algún otro valor. Pero como se puede asignar Truea otro valor, el intérprete tiene que buscarlo cada vez.

Conclusión para Python 2

Si tiene un bucle estrecho y de larga duración en Python 2, probablemente debería usar en while 1:lugar de while True:.

Conclusión para Python 3

Úselo while True:si no tiene ninguna condición para salir de su bucle.

Aaron Hall
fuente
3

Esta es una pregunta de 7 años que ya tiene una gran respuesta, pero un concepto erróneo en la pregunta, que no se aborda en ninguna de las respuestas, hace que sea potencialmente confuso para algunas de las otras preguntas marcadas como duplicadas.

En otros contextos, Python actúa como si True fuera igual a 1:

>>> True == 1
True

>>> True + True
2

¿Por qué mientras distingue a los dos?

De hecho, whileno está haciendo nada diferente aquí. Se distingue 1y Trueexactamente de la misma manera que lo hace el +ejemplo.


Aquí está 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

Ahora compare:

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

Emite un LOAD_GLOBAL (True)para cada uno True, y el optimizador no puede hacer nada con un global. Entonces, se whiledistingue 1y Trueexactamente por la misma razón que lo +hace. (Y ==no los distingue porque el optimizador no optimiza las comparaciones).


Ahora compare 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

Aquí, emite una LOAD_CONST (True)para la palabra clave, que el optimizador puede aprovechar. Entonces, True + 1 no distingue, exactamente por la misma razón while Trueque no lo hace. (Y ==todavía no los distingue porque el optimizador no optimiza las comparaciones).


Mientras tanto, si el código no está optimizado a cabo, el intérprete termina el tratamiento Truey 1exactamente el mismo en los tres de estos casos. booles una subclase de int, hereda la mayoría de sus métodos inty Truetiene un valor entero interno de 1. Entonces, ya sea que esté haciendo una whileprueba ( __bool__en 3.x, __nonzero__en 2.x), una comparación ( __eq__) o aritmética ( __add__), está llamando al mismo método ya sea que use Trueo 1.

abarnert
fuente