while (1) vs. while (True) - Por que há uma diferença (no bytecode python 2)?

115

Intrigado com esta questão sobre loops infinitos em perl: while (1) Vs. para (;;) Existe uma diferença de velocidade? , Decidi fazer uma comparação semelhante em python. Eu esperava que o compilador gerasse o mesmo código de byte para while(True): passe while(1): pass, mas esse não é realmente o caso em python2.7.

O seguinte script:

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)

produz os seguintes 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        

O uso while Trueé visivelmente mais complicado. Por que é isso?

Em outros contextos, python age como se fosse Trueigual a 1:

>>> True == 1
True

>>> True + True
2

Por que whiledistingue os dois?

Percebi que o python3 avalia as instruções usando operações 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         

Há uma mudança em python3 na forma como os booleanos são avaliados?

AndrewF
fonte

Respostas:

124

No Python 2.x, Truenão é uma palavra-chave, mas apenas uma constante global integrada que é definida como 1 no booltipo. Portanto, o intérprete ainda precisa carregar o conteúdo de True. Em outras palavras, Trueé reatribuível:

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

No Python 3.x, ele realmente se torna uma palavra-chave e uma 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

assim, o interpretador pode substituir o while True:loop por um loop infinito.

Kennytm
fonte
1
@MH: AFAIK, foi um expediente para colocar a palavra-chave na linguagem.
S.Lott
Isso significa while 1e while Truesão idênticos no Python 3?
Stevoisiak
@ StevenM.Vascellaro Sim.
kennytm
14

Isso não está certo,

assim, o interpretador pode substituir o loop while True: por um loop infinito.

porque ainda é possível sair do loop. Mas é verdade que essa elsecláusula de loop nunca seria acessada no Python 3. E também é verdade que simplificar a pesquisa de valor faz com que ele seja executado tão rapidamente quanto while 1no Python 2.

Comparação de Desempenho

Demonstrando a diferença de tempo para um loop while um tanto não trivial:

Configuração

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

Explicação

Para explicar a diferença, em Python 2:

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

mas em Python 3:

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

Como Trueé uma palavra-chave no Python 3, o interpretador não precisa consultar o valor para ver se alguém o substituiu por algum outro valor. Mas, uma vez que se pode atribuir Trueoutro valor, o intérprete deve pesquisá-lo todas as vezes.

Conclusão para Python 2

Se você tiver um loop apertado e de longa execução no Python 2, provavelmente deverá usar em while 1:vez de while True:.

Conclusão para Python 3

Use while True:se você não tiver condições de sair do loop.

Aaron Hall
fonte
3

Esta é uma pergunta de 7 anos que já tem uma ótima resposta, mas um equívoco na pergunta, que não foi abordado em nenhuma das respostas, a torna potencialmente confusa para algumas das outras perguntas marcadas como duplicatas.

Em outros contextos, python age como se True fosse igual a 1:

>>> True == 1
True

>>> True + True
2

Por que enquanto distingue os dois?

Na verdade, whilenão está fazendo nada diferente aqui. Ele distingue 1e Trueexatamente da mesma maneira que o +exemplo o faz.


Aqui 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

Agora compare:

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

Ele está emitindo um LOAD_GLOBAL (True)para cada um Truee não há nada que o otimizador possa fazer com um global. Então, whiledistingue 1e Truepela mesma razão que +faz. (E ==não os distingue porque o otimizador não otimiza as comparações.)


Agora 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

Aqui, ele está emitindo um LOAD_CONST (True)para a palavra-chave, que o otimizador pode tirar proveito. Então, True + 1 não faz distinção, exatamente pelo mesmo motivo while Truenão faz. (E ==ainda não os distingue porque o otimizador não otimiza as comparações.)


Enquanto isso, se o código não for otimizado, o intérprete acaba tratando Truee 1exatamente igual nos três casos. boolé uma subclasse de inte herda a maioria de seus métodos de inte Truetem um valor inteiro interno de 1. Então, se você estiver fazendo um whileteste ( __bool__em 3.x, __nonzero__em 2.x), uma comparação ( __eq__) ou aritmética ( __add__), você está chamando o mesmo método, quer use Trueou 1.

abarnert
fonte