为什么Python中的'and'和'or'返回操作数?

16

我正在学习《笨办法学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 具有布尔结果的操作和内置函数总是针对 false 返回 0False,针对 true 返回 1True,除非另有说明。(重要的例外情况:布尔运算符 orand 总是返回它们的一个操作数。)
根据 LPTHW:http://learnpythonthehardway.org/book/ex28.html 为什么"test" and "test"返回 "test" 或者1 and 1返回 1 而不是 True? Python 和许多类似的语言返回其布尔表达式中的一个操作数,而不仅仅是 True 或 False。这意味着如果你执行 False and 1,你会得到第一个操作数(False)但如果你执行 True and 1,你会得到第二个操作数(1)。尝试一下。

2
布尔运算也是惰性的。 and操作将在找到第一个False操作数时停止评估操作数。or操作将在找到第一个True操作数时停止评估操作数。 - wwii
如果所有操作数都评估为True,则and操作将返回最后一个操作数。or操作将返回第一个评估为True的操作数。这里有使用案例 - 记住这一点,总有一天你会恍然大悟! - wwii
4个回答

16
我认为你对文档的解释有点混淆了。看一下这两个文档部分:Truth Value Testing and Boolean Operators。引用第一个部分的最后一段话:
操作和具有布尔结果的内置函数总是返回 0False 代表假,返回 1True 代表真,除非另有说明。(重要例外:布尔运算 orand 总是返回它们的一个操作数)
正如你所看到的,你关于操作和内置函数是正确的,但请注意“重要例外”部分,其中明确指出布尔运算符将返回它们的一个操作数。
现在,它们可能返回的内容严重取决于运算符的短路逻辑。对于 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 (typeof variableName !== 'undefined') {
    // code goes here
}

if var is None:
    var = generate_name()

您可以使用单行代码:

var = var or generate_name()

由于None是一个假值,or将继续搜索并计算第二个操作数,即调用最终返回生成的名称的函数。这是一个非常愚蠢的例子,我已经看到过更好的使用方式(尽管不是在Python中)这种风格。我现在想不出更好的例子了。您还可以查看这些问题,这些问题上有关于这个主题非常有用的答案:Does Python support short-circuiting?

最后但并非最不重要的一点,这与静态类型,鸭子类型,动态,解释,编译或任何语言无关。这只是一个语言特性,可能很方便,并且几乎我能想到的每种编程语言都提供此功能。

希望这有所帮助!


问题是为什么会出现这种行为,我不认为你已经解释清楚了。 - Konrad Rudolph
@KonradRudolph 我现在正在寻找一个有用的例子,演示如何使用短路运算符进行赋值 :) - Paulo Bu
@KonradRudolph 此外,OP 说:请帮助我理解这里发生了什么,根据文档,这不应该发生... 所以我猜他/她是在寻找一个解释。 - Paulo Bu
我猜这与布尔值的“短路”特性有关。这个例子很好。我实际上看到了它的用处,相信以后在某些代码中会用到它。 - Khadijah Celestine

4

为了支持以下习惯用法,人们希望andor会被评估为操作数(而非总是评估为TrueFalse):

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”之外的其他东西。这偶尔但很少会导致误解。

在Lambda的情况下,这些习惯用法确实是完成某些事情的唯一方式。当然,你也可以辩论Lambda的价值,所以... :) - roippi
1
@roippi:它们不是唯一的方法,你可以编写另一个函数供lambda调用;-)。但是,无论好坏如何,lambda和其他允许单个表达式的上下文总是有点像只有一只手被绑在背后。因此,可能会出现更冗长的等效代码,具体取决于变化所引起的影响。 - Steve Jessop
在大多数情况下,true_result if expression else false_result 可能很有用。但这比 and/or 更新,而且 and/or 有时更短。 - glglgl

3
这与 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

1
考虑以下使用案例:
element = dict.has_key('foo') and dict['foo']

如果存在,将element设置为dict['foo'],否则设置为False。在编写一个函数返回值或False时非常有用。
进一步的用例使用or
print element or 'Not found!'

将这两行代码组合起来,如果dict['foo']存在,它就会输出该值,否则它将输出'Not found!'(我使用str()是因为当element0(或False)时or会失败,因为它被认为是falsey,而且我们只打印它,所以不重要)。
这可以简化为:
print dict.has_key('foo') and str(dict['foo']) or 'Not found!'

"并且在功能上等同于:"
if dict.has_key('foo'):
    print dict['foo']
else:
    print 'Not found!'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接