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模块,来查看关键字:
1
2
3
4
5
|
# 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不是关键字,因此我们可以对其进行任意的赋值:
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值来执行。
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
(
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循环的脚本,两个函数中,除了1和True的区别之外,其他地方完全相同。
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 -*-
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的字节码,下面的程序是对刚才的程序进行了一定的简化后的版本。
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 -*-
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
|
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,那么程序的执行效率又如何呢?
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 -*-
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的使用方式同上,因此不再给出程序):
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
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