为什么Python中的'and'和'or'返回操作数?
我在学习《通过艰难的方式学Python》(LPTHW)时遇到了一些我不太明白的地方。什么时候我们会希望布尔值的 and
或 or
返回的结果不是布尔值呢?LPTHW的内容提到,像Python这样的语言都有这种特性。这里是指解释型语言和编译型语言的区别,还是动态类型和静态类型语言的区别呢?
我运行了以下代码:
>>> False and 1
False
>>> True and 1
1
>>> 1 and False
False
>>> 1 and True
True
>>> True and 121
121
>>> False or 1
1
>>> False or 112
112
>>> False or "Khadijah"
'Khadijah'
>>> True and 'Khadijah'
'Khadijah'
>>> False or 'b'
'b'
>>> b = (1, 2, "K")
>>> b
(1, 2, 'K')
>>> False or b
(1, 2, 'K')
>>>
请帮我理解这里发生了什么。
根据文档: http://docs.python.org/2/library/stdtypes.html
所有返回布尔结果的操作和内置函数通常会返回 0
或 False
表示假,返回 1
或 True
表示真,除非另有说明。(重要的例外:布尔操作 or
和 and
总是返回它们的一个操作数。)
根据LPTHW的内容: http://learnpythonthehardway.org/book/ex28.html,为什么 "test" and "test"
返回的是 "test",而 1 and 1
返回的是 1,而不是 True 呢?Python 和许多类似的语言在布尔表达式中返回的是它们的一个操作数,而不仅仅是 True 或 False。这意味着如果你用 False 和 1 进行运算,你会得到第一个操作数(False),但如果你用 True 和 1,你会得到第二个操作数(1)。可以多试试这个。
4 个回答
考虑以下使用场景:
element = dict.has_key('foo') and dict['foo']
如果字典中存在 'foo'
这个键,就把 element
设置为 dict['foo']
的值;如果不存在,就设置为 False
。这个方法在写一个函数时很有用,可以返回一个值或者在失败时返回 False
。
再来看一个使用 or
的场景:
print element or 'Not found!'
把这两行代码放在一起,如果 'foo'
存在,就会打印出 dict['foo']
的值;如果不存在,就会打印 'Not found!'
(我使用 str()
是因为如果 element
是 0
或 False
,那么 or
会失败,因为这两者被认为是“假”的,而我们只是打印出来,所以这没关系)。
这个可以简化为:
print dict.has_key('foo') and str(dict['foo']) or 'Not found!'
并且功能上等价于:
if dict.has_key('foo'):
print dict['foo']
else:
print 'Not found!'
这和Python中短路效果的实现方式有关。
在使用and
的时候(记住True and X = X
),如果右边的表达式是假的,它会立刻被弹出;如果是真的,第二个表达式才会被弹出:
>>> import dis
>>>
>>> dis.dis(lambda : True and 0)
1 0 LOAD_CONST 2 (True)
3 JUMP_IF_FALSE_OR_POP 9
6 LOAD_CONST 1 (0)
>> 9 RETURN_VALUE
>>>
>>> True and 0
0
>>> 0 and True
0
>>>
类似于:
def exec_and(obj1, obj2):
if bool(obj1) != False:
return obj2
return obj1
而使用or
时,如果第一个表达式是真的,它会立刻被弹出。如果不是,第二个表达式才会被弹出,这时结果就完全依赖于第二个表达式。
>>> dis.dis(lambda : 0 or False)
1 0 LOAD_CONST 1 (0)
3 JUMP_IF_TRUE_OR_POP 9
6 LOAD_CONST 2 (False)
>> 9 RETURN_VALUE
>>>
>>> True or 0
True
>>> 1 or False
1
>>> False or 1
1
>>>
类似于:
def exec_or(obj1, obj2):
if bool(obj1) != True:
return obj2
return obj1
人们希望 and
和 or
这两个操作符能够返回一个操作数,而不是总是返回 True
或 False
,这样可以支持一些特定的写法,比如下面的例子:
def foo(self):
# currentfoo might be None, in which case return the default
return self.currentfoo or self.defaultfoo()
def foobar(self):
# foo() might return None, in which case return None
foo = self.foo()
return foo and foo.bar()
当然,你可以质疑这种写法的价值,特别是如果你不习惯的话。其实总是可以用明确的 if
语句来写出等效的代码。
不过,这种写法有一个缺点,就是让人不太确定是否考虑到了所有可能的“假值”,还是只考虑了评论中提到的那个(其他的假值可能不被允许)。但实际上,这种情况在使用可能不是 True
或 False
的值的代码中是普遍存在的。偶尔会导致一些误解,但并不常见。
我觉得你可能对文档的内容有些困惑。看看这两个文档部分:真值测试和布尔运算符。引用第一部分最后一段的话:
那些返回布尔结果的操作和内置函数,通常会返回
0
或False
表示假,返回1
或True
表示真,除非另有说明。(重要例外:布尔运算符or
和and
总是返回它们的某个操作数。)
如你所见,你说的操作和内置函数是对的,但注意重要例外部分,文中明确说明布尔运算符会返回它们的某个操作数。
它们返回什么,主要取决于运算符的短路逻辑。对于or
运算符,它会返回表达式中第一个为真的值,因为一旦找到一个,整个表达式就为真。如果所有操作数都是为假的,or
会返回最后一个操作数,这意味着它遍历了每一个操作数,没能找到一个为真的。
对于and
运算符,如果表达式为真,它会返回最后一个操作数;如果表达式为假,它会返回第一个为假的操作数。你可以在维基百科页面了解更多关于短路求值的信息。
你在问题中有很多例子,我们来分析其中一些:
>>> False and 1 # return false (short circuited at first falsey value)
False
>>> True and 1 # return 1 (never short circuited and return the last truthy value)
1
>>> 1 and False # return false (short circuited at first falsey value, in this case the last operand)
False
>>> 1 and True # return True (never short circuited and return the last truthy value)
True
>>> True and 121 # return 121 (never short circuited and return the last truthy value)
121
>>> False or 1 # return 1 (since the first operand was falsey, or kept looking for a first truthy value which happened to be the last operator)
1
>>> False or 112 # return 112 for same reason as above
112
>>> False or "Khadijah" # return "Khadijah" for same reason as above
'Khadijah'
>>> True and 'Khadijah' # return "Khadijah" because same reason as second example
'Khadijah'
我想这应该能说明问题。为了帮助你更好地理解这为什么有用,考虑以下例子:
你有一个函数可以随机生成名字
import random
def generate_name():
return random.choice(['John', 'Carl', 'Tiffany'])
而你有一个变量,不知道它是否已经被赋值了名字,所以你可以不必这样做:
if var is None:
var = generate_name()
你可以用一行代码来实现:
var = var or generate_name()
因为None
是一个为假的值,or
会继续查找并评估第二个操作数,也就是调用这个函数,最终返回生成的名字。这是一个很简单的例子,我见过更好的用法(虽然不是在Python中)这种风格。我现在想不出更好的例子。你也可以看看这个问题,里面有非常有用的答案:Python支持短路求值吗?
最后但同样重要的是,这与静态类型、鸭子类型、动态、解释型、编译型等语言没有关系。这只是语言的一个特性,可能会很有用,而且几乎每种我能想到的编程语言都提供这个特性。
希望这能帮到你!