更多
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']
1
2
3
4
5
# Python2 关键字
>>>importkeyword
>>>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
2
3
4
5
>>>(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 "", line 1, in
ZeroDivisionError: integer division or modulo by zero
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>>True+True
2
>>>True-True
0
>>>True*True
1
>>>(True+True)>1
True
>>>True+5
6
>>>1/False
Traceback(mostrecentcalllast):
File"",line1,in
ZeroDivisionError:integerdivisionormodulobyzero
3. While 1比While True快?
首先来看一个比较while 1和while True循环的脚本,两个函数中,除了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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#! /usr/bin/python
# -*- coding: utf-8 -*-
importtimeit
defwhile_one():
i=0
while1:
i+=1
ifi==10000000:
break
defwhile_true():
i=0
whileTrue:
i+=1
ifi==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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#! /usr/bin/python
# -*- coding: utf-8 -*-
importdis
defwhile_one():
while1:
pass
defwhile_true():
whileTrue:
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while_one
60SETUP_LOOP3(to6)
7>>3JUMP_ABSOLUTE3
>>6LOAD_CONST0(None)
9RETURN_VALUE
while_true
100SETUP_LOOP10(to13)
>>3LOAD_GLOBAL0(True)
6POP_JUMP_IF_FALSE12
119JUMP_ABSOLUTE3
>>12POP_BLOCK
>>13LOAD_CONST0(None)
16RETURN_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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#! /usr/bin/python
# -*- coding: utf-8 -*-
importtimeit
defif_x_eq_true():
x=True
ifx==True:
pass
defif_x():
x=True
ifx:
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if_x_eq_true
80LOAD_GLOBAL0(True)
3STORE_FAST0(x)
96LOAD_FAST0(x)
9LOAD_GLOBAL0(True)
12COMPARE_OP2(==)
15POP_JUMP_IF_FALSE21
1018JUMP_FORWARD0(to21)
>>21LOAD_CONST0(None)
24RETURN_VALUE
if_x
130LOAD_GLOBAL0(True)
3STORE_FAST0(x)
146LOAD_FAST0(x)
9POP_JUMP_IF_FALSE15
1512JUMP_FORWARD0(to15)
>>15LOAD_CONST0(None)
18RETURN_VALUE
可以清晰的看到第9行比第14行,多出了检查True值和进行比较的操作。
也就是说,不论从遵循PEP的规范,还是执行效率,或者程序的简洁性来说,我们都应该使用if x:,而不是if x == True:来进行比较。同理,那些if x is not None:之类的语句也应当被简化为if x:(如果要比较的是非值,而不必须是None的话)。
5. References