while (1) vs. while (True) - Warum gibt es einen Unterschied (im Python 2-Bytecode)?

115

Fasziniert von dieser Frage nach Endlosschleifen in Perl: while (1) Vs. für (;;) Gibt es einen Geschwindigkeitsunterschied? Ich habe beschlossen, einen ähnlichen Vergleich in Python durchzuführen. Ich habe erwartet, dass der Compiler den gleichen Bytecode für while(True): passund generiert while(1): pass, aber dies ist in Python2.7 eigentlich nicht der Fall.

Das folgende Skript:

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)

führt zu folgenden Ergebnissen:

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        

Die Verwendung while Trueist spürbar komplizierter. Warum ist das?

In anderen Kontexten verhält sich Python so, als wäre es Truegleich 1:

>>> True == 1
True

>>> True + True
2

Warum whileunterscheidet man die beiden?

Mir ist aufgefallen, dass python3 die Anweisungen mit identischen Operationen auswertet:

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         

Gibt es in Python3 eine Änderung in der Art und Weise, wie Boolesche Werte ausgewertet werden?

AndrewF
quelle

Antworten:

124

In Python 2.x Trueist dies kein Schlüsselwort, sondern nur eine integrierte globale Konstante , die im boolTyp auf 1 definiert ist . Daher muss der Interpreter noch den Inhalt von laden True. Mit anderen Worten, Trueist neu zuweisbar:

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

In Python 3.x wird es wirklich zu einem Schlüsselwort und einer echten Konstante:

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

Somit kann der Interpreter die while True:Schleife durch eine Endlosschleife ersetzen .

kennytm
quelle
1
@MH: AFAIK, es war zweckmäßig, das Schlüsselwort in die Sprache zu bringen.
S.Lott
Bedeutet dies while 1und while Truesind in Python 3 identisch?
Stevoisiak
@ StevenM.Vascellaro Ja.
Kennytm
14

Das ist nicht ganz richtig,

Somit kann der Interpreter die while True: -Schleife durch eine Endlosschleife ersetzen.

da kann man noch aus der schleife ausbrechen. Es ist jedoch richtig, dass auf eine solche Schleifenklausel elsein Python 3 niemals zugegriffen werden würde. Und es ist auch wahr, dass die Vereinfachung der Wertesuche dazu führt, dass sie genauso schnell ausgeführt wird wie while 1in Python 2.

Leistungsvergleich

Demonstration des Zeitunterschieds für eine etwas nicht triviale while-Schleife:

Konfiguration

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

Erläuterung

Um den Unterschied in Python 2 zu erklären:

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

aber in Python 3:

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

Da Truees sich in Python 3 um ein Schlüsselwort handelt, muss der Interpreter den Wert nicht nachschlagen, um festzustellen, ob jemand ihn durch einen anderen Wert ersetzt hat. Da man jedoch einen Trueanderen Wert zuweisen kann, muss der Interpreter ihn jedes Mal nachschlagen.

Fazit für Python 2

Wenn Sie in Python 2 eine enge, lang laufende Schleife haben, sollten Sie wahrscheinlich while 1:anstelle von verwenden while True:.

Fazit für Python 3

Verwenden while True:Sie diese Option, wenn Sie keine Bedingung zum Ausbrechen Ihrer Schleife haben.

Aaron Hall
quelle
3

Dies ist eine 7 Jahre alte Frage, die bereits eine gute Antwort hat, aber ein Missverständnis in der Frage, das in keiner der Antworten angesprochen wird, macht sie möglicherweise verwirrend für einige der anderen Fragen, die als Duplikate markiert sind.

In anderen Kontexten verhält sich Python so, als wäre True gleich 1:

>>> True == 1
True

>>> True + True
2

Warum unterscheidet man dabei die beiden?

Tatsächlich whilemacht man hier überhaupt nichts anderes. Es unterscheidet 1und Truegenauso wie das +Beispiel.


Hier ist 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

Vergleichen Sie nun:

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

Es gibt LOAD_GLOBAL (True)für jeden ein a aus True, und der Optimierer kann mit einem globalen Objekt nichts anfangen. Also, whileunterscheidet 1und Trueaus genau dem gleichen Grund, der es +tut. (Und ==unterscheidet sie nicht, weil der Optimierer Vergleiche nicht optimiert.)


Vergleichen Sie nun 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

Hier wird ein LOAD_CONST (True)Schlüsselwort für das Schlüsselwort ausgegeben, das der Optimierer nutzen kann . Also True + 1 nicht unterscheiden, aus genau dem gleichen Grund while Truenicht. (Und ==unterscheidet sie immer noch nicht, weil der Optimierer Vergleiche nicht optimiert.)


Inzwischen, wenn der Code nicht optimiert geführt wird, endet der Dolmetscher auf die Behandlung Trueund 1genau das gleiche in allen drei dieser Fälle. boolist eine Unterklasse von intund erbt die meisten ihrer Methoden von intund Truehat einen internen ganzzahligen Wert von 1. Egal, ob Sie einen whileTest ( __bool__in 3.x, __nonzero__in 2.x), einen Vergleich ( __eq__) oder eine Arithmetik durchführen ( __add__), Sie rufen dieselbe Methode auf, unabhängig davon, ob Sie Trueoder verwenden 1.

abarnert
quelle