while (1) vs. while (True) - Dlaczego istnieje różnica (w kodzie bajtowym Pythona 2)?

115

Zaintrygowany pytaniem o nieskończone pętle w Perlu: while (1) vs. dla (;;) Czy istnieje różnica prędkości? , Postanowiłem przeprowadzić podobne porównanie w Pythonie. Spodziewałem się, że kompilator wygeneruje ten sam kod bajtowy dla while(True): passi while(1): pass, ale w rzeczywistości tak nie jest w pythonie2.7.

Poniższy skrypt:

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)

daje następujące wyniki:

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        

Korzystanie while Truejest zauważalnie bardziej skomplikowane. Dlaczego to?

W innych kontekstach python działa tak, jakby był Truerówny 1:

>>> True == 1
True

>>> True + True
2

Dlaczego whilerozróżnia te dwa?

Zauważyłem, że python3 ocenia instrukcje przy użyciu identycznych operacji:

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         

Czy w pythonie3 nastąpiła zmiana w sposobie oceny wartości logicznych?

AndrewF
źródło

Odpowiedzi:

124

W Pythonie 2.x Truenie jest słowem kluczowym, ale tylko wbudowaną stałą globalną, która jest zdefiniowana jako 1 w booltypie. Dlatego tłumacz nadal musi załadować zawartość True. Innymi słowy, Truemożna ponownie przypisać:

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

W Pythonie 3.x naprawdę staje się słowem kluczowym i prawdziwą stałą:

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

w ten sposób interpreter może zastąpić while True:pętlę nieskończoną pętlą.

kennytm
źródło
1
@MH: AFAIK, celowe było wprowadzenie słowa kluczowego do języka.
S.Lott,
Czy to oznacza while 1i while Truesą identyczne w Pythonie 3?
Stevoisiak
@ StevenM.Vascellaro Tak.
kennytm
14

To nie jest całkiem w porządku

w ten sposób interpreter może zastąpić pętlę while True: nieskończoną pętlą.

ponieważ wciąż można wyrwać się z pętli. Ale prawdą jest, że elseklauzula takiej pętli nigdy nie byłaby dostępna w Pythonie 3. Prawdą jest również, że uproszczenie wyszukiwania wartości sprawia, że ​​działa tak szybko, jak while 1w Pythonie 2.

Porównanie wydajności

Demonstrowanie różnicy w czasie dla nieco nietrywialnej pętli while:

Ustawiać

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

Wyjaśnienie

Aby wyjaśnić różnicę, w Pythonie 2:

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

ale w Pythonie 3:

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

Ponieważ Truejest to słowo kluczowe w Pythonie 3, interpreter nie musi wyszukiwać wartości, aby zobaczyć, czy ktoś zastąpił ją inną wartością. Ale ponieważ można przypisać Trueinną wartość, tłumacz musi za każdym razem ją sprawdzać.

Wniosek dotyczący Pythona 2

Jeśli masz zwartą, długotrwałą pętlę w Pythonie 2, prawdopodobnie powinieneś użyć while 1:zamiast while True:.

Wniosek dotyczący Pythona 3

Użyj, while True:jeśli nie masz warunków do wyrwania się z pętli.

Aaron Hall
źródło
3

To jest pytanie sprzed 7 lat, na które już znalazła się świetna odpowiedź, ale błędne przekonanie w nim, które nie zostało uwzględnione w żadnej z odpowiedzi, sprawia, że ​​może być mylące w przypadku niektórych innych pytań oznaczonych jako duplikaty.

W innych kontekstach Python zachowuje się tak, jakby True równa się 1:

>>> True == 1
True

>>> True + True
2

Dlaczego rozróżnia te dwa?

Właściwie whilenie robi tu nic innego. Wyróżnia 1i Truedokładnie tak samo, jak +robi to przykład.


Oto 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

Teraz porównaj:

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

Emituje znak LOAD_GLOBAL (True)dla każdego Truei optymalizator nie może nic zrobić z globalnym. Więc whilerozróżnia 1i Truez tego samego powodu co +robi. (I ==nie rozróżnia ich, ponieważ optymalizator nie optymalizuje porównań).


Teraz porównaj 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

Tutaj emituje znak LOAD_CONST (True)dla słowa kluczowego, z którego może skorzystać optymalizator . Więc True + 1 nie rozróżnia, z dokładnie tego samego powodu while Truenie. (I ==nadal ich nie rozróżnia, ponieważ optymalizator nie optymalizuje porównań).


Tymczasem, jeśli kod nie jest zoptymalizowany się, interpreter kończy się leczeniu Truei 1dokładnie taka sama we wszystkich tych trzech przypadkach. booljest podklasą inti dziedziczy większość swoich metod z inti Truema wewnętrzną wartość całkowitą 1. Czy więc wykonujesz whiletest ( __bool__w 3.x, __nonzero__w 2.x), porównanie ( __eq__) czy arytmetykę ( __add__), wywołujesz tę samą metodę, niezależnie od tego, czy używasz, Trueczy 1.

abarnert
źródło