Python 研究-while 1比while True更快?
0. 前言
前些天被Python的多線程坑了一把,因此產生了寫一個《Python天坑系列》博客的想法,說說我碰到的那些Python的坑。
而天坑這個詞呢,一方面指Python的坑,另一方面也說明本系列文章也是個坑,對於會寫什麼內容、有多少篇、多久更新一次、什麼時間更新我都無法確定,哈哈(看,之前已經3個月沒有更新過了!)。
本篇是系列的第一篇,講的內容是Python的bool類型。
1. 前提
1.1 bool是int的子類
根據PEP285中Review部分第6條所述,bool類是從int類繼承而來的,這樣可以極大的簡化實現(C程式碼中呼叫PyInt_Check()的地方仍將繼續工作)。
1.2 Python2中True/False不是關鍵字,但Python3中是
我們可以導入keyword模組,來查看關鍵字:
keyword
Python
# Python2 ??字
>>> import keyword
>>> keyword.kwlist
>>> ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
# Python2 關鍵字
>>> import keyword
>>> keyword.kwlist
>>> ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
而在Python3中,關鍵字中增加了True/False/None。
由於Python2中True/False不是關鍵字,因此我們可以對其進行任意的賦值:
Python2 True賦值
Python
>>> (1 == 1) == True
True
>>> True = "pythoner.com"
>>> (1 == 1) == True
False
>>> (1 == 1) == True
True
>>> True = "pythoner.com"
>>> (1 == 1) == True
False
2. True + True = 2
由於bool是繼承自int的子類,因此為了保證向下兼容性,在進行算術運算中,True/False會被當作int值來執行。
True + True = 2
Python
>>> True + True
2
>>> True - True
0
>>> True * True
1
>>> (True + True) > 1
True
>>> True + 5
6
>>> 1 / False
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> True + True
2
>>> True - True
0
>>> True * True
1
>>> (True + True) > 1
True
>>> True + 5
6
>>> 1 / False
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
3. While 1比While True快?
首先來看一個比較while 1和while True循環的scripts,兩個函數中,除了1和True的區別之外,其他地方完全相同。
while 1與while True比較
Python
#! /usr/bin/python
# -*- coding: utf-8 -*-
import timeit
def while_one():
i = 0
while 1:
i += 1
if i == 10000000:
break
def while_true():
i = 0
while True:
i += 1
if i == 10000000:
break
if __name__ == "__main__":
w1 = timeit.timeit(while_one, "from __main__ import while_one", number=3)
wt = timeit.timeit(while_true, "from __main__ import while_true", number=3)
print "while one: %s\nwhile_true: %s" % (w1, wt)
#! /usr/bin/python
# -*- coding: utf-8 -*-
import timeit
def while_one():
i = 0
while 1:
i += 1
if i == 10000000:
break
def while_true():
i = 0
while True:
i += 1
if i == 10000000:
break
if __name__ == "__main__":
w1 = timeit.timeit(while_one, "from __main__ import while_one", number=3)
wt = timeit.timeit(while_true, "from __main__ import while_true", number=3)
print "while one: %s\nwhile_true: %s" % (w1, wt)
執行結果:
while one: 1.37000703812
while_true: 2.07638716698
可以看出wihle 1的執行時間約為while True的2/3。
那麼,這是為什麼呢?
其實這就是前提中提到的關鍵字的問題。由於Python2中,True/False不是關鍵字,因此我們可以對其進行任意的賦值,這就導致程式在每次循環時都需要對True/False的值進行檢查;而對於1,則被程式進行了優化,而後不會再進行檢查。
我們可以通過dis模組來查看while_one和while_true的字元碼,下面的程式是對剛才的程式進行了一定的簡化後的版本。
while 1和while True的字元碼程式
Python
#! /usr/bin/python
# -*- coding: utf-8 -*-
import dis
def while_one():
while 1:
pass
def while_true():
while True:
pass
if __name__ == "__main__":
print "while_one\n"
dis.dis(while_one)
print "while_true\n"
dis.dis(while_true)
#! /usr/bin/python
# -*- coding: utf-8 -*-
import dis
def while_one():
while 1:
pass
def while_true():
while True:
pass
if __name__ == "__main__":
print "while_one\n"
dis.dis(while_one)
print "while_true\n"
dis.dis(while_true)
執行的結果是:
while 1和while True的字元碼執行結果
Python
while_one
6 0 SETUP_LOOP 3 (to 6)
7 >> 3 JUMP_ABSOLUTE 3
>> 6 LOAD_CONST 0 (None)
9 RETURN_VALUE
while_true
10 0 SETUP_LOOP 10 (to 13)
>> 3 LOAD_GLOBAL 0 (True)
6 POP_JUMP_IF_FALSE 12
11 9 JUMP_ABSOLUTE 3
>> 12 POP_BLOCK
>> 13 LOAD_CONST 0 (None)
16 RETURN_VALUE
while_one
6 0 SETUP_LOOP 3 (to 6)
7 >> 3 JUMP_ABSOLUTE 3
>> 6 LOAD_CONST 0 (None)
9 RETURN_VALUE
while_true
10 0 SETUP_LOOP 10 (to 13)
>> 3 LOAD_GLOBAL 0 (True)
6 POP_JUMP_IF_FALSE 12
11 9 JUMP_ABSOLUTE 3
>> 12 POP_BLOCK
>> 13 LOAD_CONST 0 (None)
16 RETURN_VALUE
可以看出,正如上面所講到的,在while True的時候,字元碼中多出了幾行語句,正是這幾行語句進行了True值的檢查。
而在Python3中,由於True/False已經是關鍵字了,不允許進行重新賦值,因此,其執行結果與while 1不再有區別(好吧,我這沒有Python3的環境,就不去驗證了,網上有人驗證過了)。但是由於Python2的使用十分廣泛,因此大家不得不注意這個可能會降低性能的地方。
4. if x == True: 還是 if x:
在PEP285中,還提到了這兩種寫法的比較。PEP285中認為,==具有傳遞性,a==b, b==c會被化簡為a==c。也就是說,如果選擇前一種寫法的話,6和7在if語句中都應該被認為是真值,那麼就會造成6==True==7,被化簡為6==7的問題,因此後一種寫法才是正確的。
現在,讓我們偏個題,假設x就是True,那麼程式的執行效率又如何呢?
if x == True:和if x:比較
Python
#! /usr/bin/python
# -*- coding: utf-8 -*-
import timeit
def if_x_eq_true():
x = True
if x == True:
pass
def if_x():
x = True
if x:
pass
if __name__ == "__main__":
if1 = timeit.timeit(if_x_eq_true, "from __main__ import if_x_eq_true", number = 1000000)
if2 = timeit.timeit(if_x, "from __main__ import if_x", number = 1000000)
print "if_x_eq_true: %s\nif_x: %s" % (if1, if2)
#! /usr/bin/python
# -*- coding: utf-8 -*-
import timeit
def if_x_eq_true():
x = True
if x == True:
pass
def if_x():
x = True
if x:
pass
if __name__ == "__main__":
if1 = timeit.timeit(if_x_eq_true, "from __main__ import if_x_eq_true", number = 1000000)
if2 = timeit.timeit(if_x, "from __main__ import if_x", number = 1000000)
print "if_x_eq_true: %s\nif_x: %s" % (if1, if2)
執行結果是:
if_x_eq_true: 0.212558031082
if_x: 0.144327878952
讓我們再來看看字元碼(程式未作修改,dis的使用方式同上,因此不再給出程式):
if x == True:和if x:的字元碼
Python
if_x_eq_true
8 0 LOAD_GLOBAL 0 (True)
3 STORE_FAST 0 (x)
9 6 LOAD_FAST 0 (x)
9 LOAD_GLOBAL 0 (True)
12 COMPARE_OP 2 (==)
15 POP_JUMP_IF_FALSE 21
10 18 JUMP_FORWARD 0 (to 21)
>> 21 LOAD_CONST 0 (None)
24 RETURN_VALUE
if_x
13 0 LOAD_GLOBAL 0 (True)
3 STORE_FAST 0 (x)
14 6 LOAD_FAST 0 (x)
9 POP_JUMP_IF_FALSE 15
15 12 JUMP_FORWARD 0 (to 15)
>> 15 LOAD_CONST 0 (None)
18 RETURN_VALUE
if_x_eq_true
8 0 LOAD_GLOBAL 0 (True)
3 STORE_FAST 0 (x)
9 6 LOAD_FAST 0 (x)
9 LOAD_GLOBAL 0 (True)
12 COMPARE_OP 2 (==)
15 POP_JUMP_IF_FALSE 21
10 18 JUMP_FORWARD 0 (to 21)
>> 21 LOAD_CONST 0 (None)
24 RETURN_VALUE
if_x
13 0 LOAD_GLOBAL 0 (True)
3 STORE_FAST 0 (x)
14 6 LOAD_FAST 0 (x)
9 POP_JUMP_IF_FALSE 15
15 12 JUMP_FORWARD 0 (to 15)
>> 15 LOAD_CONST 0 (None)
18 RETURN_VALUE
可以清晰的看到第9行比第14行,多出了檢查True值和進行比較的操作。
也就是說,不論從遵循PEP的規範,還是執行效率,或者程式的簡潔性來說,我們都應該使用if x:,而不是if x == True:來進行比較。同理,那些if x is not None:之類的語句也應當被簡化為if x:(如果要比較的是非值,而不必須是None的話)。
5. References
http://legacy.python.org/dev/peps/pep-0285/
http://stackoverflow.com/questions/3815359/while-1-vs-for-whiletrue-why-is-there-a-difference
前些天被Python的多線程坑了一把,因此產生了寫一個《Python天坑系列》博客的想法,說說我碰到的那些Python的坑。
而天坑這個詞呢,一方面指Python的坑,另一方面也說明本系列文章也是個坑,對於會寫什麼內容、有多少篇、多久更新一次、什麼時間更新我都無法確定,哈哈(看,之前已經3個月沒有更新過了!)。
本篇是系列的第一篇,講的內容是Python的bool類型。
1. 前提
1.1 bool是int的子類
根據PEP285中Review部分第6條所述,bool類是從int類繼承而來的,這樣可以極大的簡化實現(C程式碼中呼叫PyInt_Check()的地方仍將繼續工作)。
1.2 Python2中True/False不是關鍵字,但Python3中是
我們可以導入keyword模組,來查看關鍵字:
keyword
Python
# Python2 ??字
>>> import keyword
>>> keyword.kwlist
>>> ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
# Python2 關鍵字
>>> import keyword
>>> keyword.kwlist
>>> ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
而在Python3中,關鍵字中增加了True/False/None。
由於Python2中True/False不是關鍵字,因此我們可以對其進行任意的賦值:
Python2 True賦值
Python
>>> (1 == 1) == True
True
>>> True = "pythoner.com"
>>> (1 == 1) == True
False
>>> (1 == 1) == True
True
>>> True = "pythoner.com"
>>> (1 == 1) == True
False
2. True + True = 2
由於bool是繼承自int的子類,因此為了保證向下兼容性,在進行算術運算中,True/False會被當作int值來執行。
True + True = 2
Python
>>> True + True
2
>>> True - True
0
>>> True * True
1
>>> (True + True) > 1
True
>>> True + 5
6
>>> 1 / False
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> True + True
2
>>> True - True
0
>>> True * True
1
>>> (True + True) > 1
True
>>> True + 5
6
>>> 1 / False
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
3. While 1比While True快?
首先來看一個比較while 1和while True循環的scripts,兩個函數中,除了1和True的區別之外,其他地方完全相同。
while 1與while True比較
Python
#! /usr/bin/python
# -*- coding: utf-8 -*-
import timeit
def while_one():
i = 0
while 1:
i += 1
if i == 10000000:
break
def while_true():
i = 0
while True:
i += 1
if i == 10000000:
break
if __name__ == "__main__":
w1 = timeit.timeit(while_one, "from __main__ import while_one", number=3)
wt = timeit.timeit(while_true, "from __main__ import while_true", number=3)
print "while one: %s\nwhile_true: %s" % (w1, wt)
#! /usr/bin/python
# -*- coding: utf-8 -*-
import timeit
def while_one():
i = 0
while 1:
i += 1
if i == 10000000:
break
def while_true():
i = 0
while True:
i += 1
if i == 10000000:
break
if __name__ == "__main__":
w1 = timeit.timeit(while_one, "from __main__ import while_one", number=3)
wt = timeit.timeit(while_true, "from __main__ import while_true", number=3)
print "while one: %s\nwhile_true: %s" % (w1, wt)
執行結果:
while one: 1.37000703812
while_true: 2.07638716698
可以看出wihle 1的執行時間約為while True的2/3。
那麼,這是為什麼呢?
其實這就是前提中提到的關鍵字的問題。由於Python2中,True/False不是關鍵字,因此我們可以對其進行任意的賦值,這就導致程式在每次循環時都需要對True/False的值進行檢查;而對於1,則被程式進行了優化,而後不會再進行檢查。
我們可以通過dis模組來查看while_one和while_true的字元碼,下面的程式是對剛才的程式進行了一定的簡化後的版本。
while 1和while True的字元碼程式
Python
#! /usr/bin/python
# -*- coding: utf-8 -*-
import dis
def while_one():
while 1:
pass
def while_true():
while True:
pass
if __name__ == "__main__":
print "while_one\n"
dis.dis(while_one)
print "while_true\n"
dis.dis(while_true)
#! /usr/bin/python
# -*- coding: utf-8 -*-
import dis
def while_one():
while 1:
pass
def while_true():
while True:
pass
if __name__ == "__main__":
print "while_one\n"
dis.dis(while_one)
print "while_true\n"
dis.dis(while_true)
執行的結果是:
while 1和while True的字元碼執行結果
Python
while_one
6 0 SETUP_LOOP 3 (to 6)
7 >> 3 JUMP_ABSOLUTE 3
>> 6 LOAD_CONST 0 (None)
9 RETURN_VALUE
while_true
10 0 SETUP_LOOP 10 (to 13)
>> 3 LOAD_GLOBAL 0 (True)
6 POP_JUMP_IF_FALSE 12
11 9 JUMP_ABSOLUTE 3
>> 12 POP_BLOCK
>> 13 LOAD_CONST 0 (None)
16 RETURN_VALUE
while_one
6 0 SETUP_LOOP 3 (to 6)
7 >> 3 JUMP_ABSOLUTE 3
>> 6 LOAD_CONST 0 (None)
9 RETURN_VALUE
while_true
10 0 SETUP_LOOP 10 (to 13)
>> 3 LOAD_GLOBAL 0 (True)
6 POP_JUMP_IF_FALSE 12
11 9 JUMP_ABSOLUTE 3
>> 12 POP_BLOCK
>> 13 LOAD_CONST 0 (None)
16 RETURN_VALUE
可以看出,正如上面所講到的,在while True的時候,字元碼中多出了幾行語句,正是這幾行語句進行了True值的檢查。
而在Python3中,由於True/False已經是關鍵字了,不允許進行重新賦值,因此,其執行結果與while 1不再有區別(好吧,我這沒有Python3的環境,就不去驗證了,網上有人驗證過了)。但是由於Python2的使用十分廣泛,因此大家不得不注意這個可能會降低性能的地方。
4. if x == True: 還是 if x:
在PEP285中,還提到了這兩種寫法的比較。PEP285中認為,==具有傳遞性,a==b, b==c會被化簡為a==c。也就是說,如果選擇前一種寫法的話,6和7在if語句中都應該被認為是真值,那麼就會造成6==True==7,被化簡為6==7的問題,因此後一種寫法才是正確的。
現在,讓我們偏個題,假設x就是True,那麼程式的執行效率又如何呢?
if x == True:和if x:比較
Python
#! /usr/bin/python
# -*- coding: utf-8 -*-
import timeit
def if_x_eq_true():
x = True
if x == True:
pass
def if_x():
x = True
if x:
pass
if __name__ == "__main__":
if1 = timeit.timeit(if_x_eq_true, "from __main__ import if_x_eq_true", number = 1000000)
if2 = timeit.timeit(if_x, "from __main__ import if_x", number = 1000000)
print "if_x_eq_true: %s\nif_x: %s" % (if1, if2)
#! /usr/bin/python
# -*- coding: utf-8 -*-
import timeit
def if_x_eq_true():
x = True
if x == True:
pass
def if_x():
x = True
if x:
pass
if __name__ == "__main__":
if1 = timeit.timeit(if_x_eq_true, "from __main__ import if_x_eq_true", number = 1000000)
if2 = timeit.timeit(if_x, "from __main__ import if_x", number = 1000000)
print "if_x_eq_true: %s\nif_x: %s" % (if1, if2)
執行結果是:
if_x_eq_true: 0.212558031082
if_x: 0.144327878952
讓我們再來看看字元碼(程式未作修改,dis的使用方式同上,因此不再給出程式):
if x == True:和if x:的字元碼
Python
if_x_eq_true
8 0 LOAD_GLOBAL 0 (True)
3 STORE_FAST 0 (x)
9 6 LOAD_FAST 0 (x)
9 LOAD_GLOBAL 0 (True)
12 COMPARE_OP 2 (==)
15 POP_JUMP_IF_FALSE 21
10 18 JUMP_FORWARD 0 (to 21)
>> 21 LOAD_CONST 0 (None)
24 RETURN_VALUE
if_x
13 0 LOAD_GLOBAL 0 (True)
3 STORE_FAST 0 (x)
14 6 LOAD_FAST 0 (x)
9 POP_JUMP_IF_FALSE 15
15 12 JUMP_FORWARD 0 (to 15)
>> 15 LOAD_CONST 0 (None)
18 RETURN_VALUE
if_x_eq_true
8 0 LOAD_GLOBAL 0 (True)
3 STORE_FAST 0 (x)
9 6 LOAD_FAST 0 (x)
9 LOAD_GLOBAL 0 (True)
12 COMPARE_OP 2 (==)
15 POP_JUMP_IF_FALSE 21
10 18 JUMP_FORWARD 0 (to 21)
>> 21 LOAD_CONST 0 (None)
24 RETURN_VALUE
if_x
13 0 LOAD_GLOBAL 0 (True)
3 STORE_FAST 0 (x)
14 6 LOAD_FAST 0 (x)
9 POP_JUMP_IF_FALSE 15
15 12 JUMP_FORWARD 0 (to 15)
>> 15 LOAD_CONST 0 (None)
18 RETURN_VALUE
可以清晰的看到第9行比第14行,多出了檢查True值和進行比較的操作。
也就是說,不論從遵循PEP的規範,還是執行效率,或者程式的簡潔性來說,我們都應該使用if x:,而不是if x == True:來進行比較。同理,那些if x is not None:之類的語句也應當被簡化為if x:(如果要比較的是非值,而不必須是None的話)。
5. References
http://legacy.python.org/dev/peps/pep-0285/
http://stackoverflow.com/questions/3815359/while-1-vs-for-whiletrue-why-is-there-a-difference
留言
張貼留言