上回书说到
在前不久写下的文章《[Python]判断序列是否为空的正确姿势》中,我们曾讨论如下用于判断一个列表是否为空的代码:
# 判断a是否为空列表
a = []
if not a:
print('This list is empty!')
# 等价于
a = []
if len(a) == 0:
print('This list is empty!')
现在我们深入思考一下,在这段代码中,Python当然是不能像肚子里的蛔虫一样,知道我们用if
语句的目的是判断a
的长度是否为0,那么它究竟是通过什么样的机制来对一个list
类型的变量进行True/False
的真值推断呢?进一步地,对于其他所有对象的真值推断,Python是否有一个统一的机制呢?
答案是:Python确实有一个简单而有效的机制对所有对象进行隐式布尔值(implicit booleanness)的推断,下面将进行进一步介绍。
文档是程序猿最好的朋友
Python的官方文档对真值测试(Truth Value Testing)进行了严格的描述,摘录如下:
https:// docs.python.org/3/libra ry/stdtypes.html#truth
Any object can be tested for truth value, for use in anif
orwhile
condition or as operand of the Boolean operations below.
By default, an object is considered true unless its class defines either a__bool__()
method that returnsFalse
or a__len__()
method that returns zero, when called with the object. Here are most of the built-in objects considered false:
- constants defined to be false:
None
andFalse
. - zero of any numeric type:
0
,0.0
,0j
,Decimal(0)
,Fraction(0, 1)
- empty sequences and collections:
''
,()
,[]
,{}
,set()
,range(0)
Operations and built-in functions that have a Boolean result always return 0
or False
for false and 1
or True
for true, unless otherwise stated. (Important exception: the Boolean operations or
and and
always return one of their operands.)
任何对象都可以在if
,while
语句或and
,or
等布尔操作符中进行真值测试(Truth Value Testing),测试的结果默认是True
,除非一下两种情况的任何一种发生:
- 该对象的类定义了
__bool__
函数,且该函数返回False
- 该对象的类定义了
__len__
函数,且该函数返回0
另外,以下内置对象被认为是False
:
- 常量
None
和False
- 值为0的数值类型:
0
,0.0
,0j
,Decimal(0)
,Feaction(0, 1)
- 空的序列和容器类型:
''
,()
,[]
,{}
,set()
,range(0)
文档的优势在于简洁严谨,而本文的最终目的,是把问题讲清楚,讲明白。
__bool__
和 __len__
__bool__
和__len__
是Python中的两个魔术方法(magic method)。所谓魔术方法,就是Python中一般不会被直接显示调用,而是通过类的其他行为隐式调用的一类特殊方法。也就是说,这类方法在Python中有特殊的用途,即使你没有主动调用,他们也会不知不觉地被其他行为触发调用,比如今天将要介绍的__bool__
和__len__
,你可能之前还不知道,当你用if
或while
对某个对象进行真值判定时,这两个函数竟然可能会被调用!
所以,如果你在类中定义了这些方法的同名方法,请确定自己知道自己在做什么,否则程序可能会产生奇怪的行为~你可能会问,那我每次在自己定义方法时还需要去查表以保证我的命名没有冲突吗?不用担心,魔术方法有个显著的共同特点,就是名称总是由一对双下划线包围,这样做正是为了避免命名冲突。你只需保证自己命名的函数不以双下划线开头并结尾即可。
关于魔术方法的更多秘密,我们留待以后再专门讨论,这里只介绍今天将要用到的两个。
__bool__
函数是用于表征类的真值属性的方法,如果要在类中定义__bool__
函数,你应该保证:
- 该方法返回一个布尔值
True
或False
,否则被调用时很可能抛出TypeError
异常。 - 该方法逻辑上符合你对该类的真值的设定,否则你无法利用它得到一个有意义的真值表征。
注意:__bool__
函数是Python3的魔术方法,而在Python2
中,对应的是__nonzero__
。
__len__
函数是用于表征类的长度属性的方法,如果要在类中定义__len__
函数,你应该保证:
- 该方法返回一个
int
类型,否则被调用时很可能抛出TypeError
异常。 - 该方法逻辑上符合你对该类的长度的设定,否则你无法利用它得到一个有意义的长度表征。
值得一提的是,Python的内置函数len
就是隐式调用类的__len__
方法:
class A():
def __len__(self):
print('__len__ method in A is called')
return 250
a = A()
print(len(a))
'''
执行结果:
__len__ method in A is called
250
'''
class B():
# 没有定义__len__函数
pass
b = B()
print(len(B))
'''
执行结果:
AttributeError: B instance has
no attribute '__len__'
'''
真值测试的运行机制
了解了两种魔术方法后,我们对真值测试的流程做一个介绍。假设我们要对一个Python对象进行真值测试:
- 如果该对象定义了
__bool__
方法,则依__bool__
方法返回的真值作为该对象的真值。 - 否则,如果该对象定义了
__len__
方法,则根据__len__
方法返回的整数进行判断,返回值为0
则真值为False
,不为0
则真值为True
- 否则,该对象的真值为
True
下面通过代码向大家直观展示:
class A():
def __bool__(self):
print('__bool__ is called')
return True
def __len__(self):
print('__len__ is called')
return 0
a = A()
if a:
print('a is True')
else:
print('a is False')
'''
执行结果:
__bool__ is called
a is True
'''
上述代码中,类A
同时定义了__bool__
和__len__
方法,但根据真值测试的优先级,优先采用__bool__
作为判断依据,__len__
并没有被调用。
class B():
def __len__(self):
print('__len__ is called')
return 0
b = B()
if b:
print('b is True')
else:
print('b is False')
'''
执行结果:
__len__ is called
b is False
'''
上述代码中,类B
只定义了__len__
方法,则调用__len__
进行真值测试,由于该方法返回值为0
,对象b
被判断为False
。
class C():
pass
c = C()
if c:
print('c is True')
else:
print('c is False')
'''
执行结果:
c is True
'''
上述代码中,类C
没有定义__bool__
和__len__
函数,则默认返回True
。
>>> int(3).__bool__()
True
>>> int(0).__bool__()
False
>>> list().__bool__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__bool__'
>>> list().__len__()
0
上述代码则展示了内置数据类型int
和list
的真值测试,其中int
是定义了__bool__
函数的,list
没有定义__bool__
但定义了__len__
,到这里我们也能理解为什么空列表会被判断成False
了。
内置函数bool()
Python有一个内置函数bool()
,它接受一个对象作为参数,返回的就是该对象在Python下进行真值测试的结果,即该函数返回一个bool
变量,该变量的值的判断方法与上面介绍的真值测试完全一致。
if a:
print('a is True')
else:
print('a is False')
# 等价于
x = bool(a)
if x is True:
print('a is True')
else:
print('a is False')
总结
Python提供了一个简单有效的真值测试机制,来得到一切对象的隐式布尔值。该机制借助两个魔术方法__bool__
和__len__
,如果我们希望给自定义的类赋予符合一定逻辑的布尔值,可以通过定义这两个魔术方法来完成。Python也提供了一个内置函数bool
,使得对象的隐式布尔值可以通过该函数显式计算得到,而不一定只能用于if
,while
等过程。