"and"和"or"如何与非布尔值一起使用?

145

我想学习Python并发现了一些非常简洁但不完全理解的代码。

上下文是:

def fn(*args):
    return len(args) and max(args)-min(args)

我明白它在做什么,但为什么Python会返回它的值而不是True / False?

10 and 7-2

返回5。同样,将and更改为or会导致功能的变化。因此

10 or 7 - 2

将返回10。

这种方式是否合法/可靠,还是存在任何潜在问题?


1
and(以及or)不仅限于处理或返回布尔值。 - cs95
1
在我看来,这种写法有点令人困惑;我无法立即判断它是否应该返回一个布尔值(是否存在明显的最小值和最大值),还是一个数字(最小值和最大值之间的差异是多少)。如果是后者,则还有一个问题,即将零长度列表的差异作为数字给出是否有任何意义。(而不是None或异常) - ilkkachu
9
其他人已经解释过,它是有效的,但可能存在一个问题,即如果它返回0,则无法判断args是否为空或非空但所有元素相等。 - Especially Lime
@EspeciallyLime:没错。我在我的答案中提到了这一点。 - Eric Duminil
相关内容:https://dev59.com/plkS5IYBdhLWcg3wXFg9 - Karl Knechtel
显示剩余2条评论
8个回答

198

TL;DR

We will discuss the behaviour of logical operators and and or, where they return the first Falsy or Truthy value, respectively. The not operator always returns a boolean value. See the docs for more information.

"Truthiness", and "Truthy" Evaluations

The statement

len(args) and max(args) - min(args)

如果args不为空,则返回max(args) - min(args)的结果,否则返回0。这是一种非常"pythonic"(即Python语言风格)且简洁的表达方式,虽然可能更难读懂。通常,它是一个if-else表达式的更简洁表示。例如:

exp1 and exp2
应该(大致)翻译为:
r1 = exp1
if r1:
    r1 = exp2

或者等价地说,

r1 = exp2 if exp1 else exp1

同样地,

exp1 or exp2
应该(大致)翻译为:
r1 = exp1
if not r1:
    r1 = exp2

或者等价地说,

r1 = exp1 if exp1 else exp2

在Python中,exp1exp2是任意的Python对象或返回某些对象的表达式。理解逻辑运算符andor的使用的关键在于理解它们不仅仅局限于操作或返回布尔值。任何具有真值的对象都可以在这里进行测试。这包括intstrlistdicttuplesetNoneType和用户定义的对象。短路规则仍然适用。

那什么是真值?
当对象在条件表达式中使用时,它们的求值被称为真值。@Patrick Haugh在这篇帖子中很好地总结了真值的含义。

除了以下内容以外,所有值都被视为“真值”,而以下内容被视为“假值”:

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - 空的list
  • {} - 空的dict
  • () - 空的tuple
  • '' - 空的str
  • b'' - 空的bytes
  • set() - 空的set
  • 空的range,例如range(0)
  • 对象满足以下条件之一:
    • obj.__bool__()返回False
    • obj.__len__()返回0

一个“真值”将满足由ifwhile语句执行的检查。我们使用“真值”和“假值”来区分boolTrueFalse


and的工作原理

我们以OP的问题为起点,进入一个关于这些运算符如何工作的讨论。

给定函数定义

def foo(*args):
    ...

如何返回零个或多个参数列表中最小值和最大值之间的差异?

找到最小值和最大值很容易(使用内置函数!)。唯一的问题在于适当处理参数列表可能为空的情况(例如,调用foo())。由于and运算符,我们可以在一行代码中解决这两个问题:

def foo(*args):
     return len(args) and max(args) - min(args)
foo(1, 2, 3, 4, 5)
# 4

foo()
# 0

由于使用了and,如果第一个表达式为True,则必须继续评估第二个表达式。请注意,如果第一个表达式评估为真值,返回值始终为第二个表达式的结果。如果第一个表达式评估为假值,则返回的结果是第一个表达式的结果。

在上面的函数中,如果foo接收到一个或多个参数,len(args)大于0(一个正数),则返回max(args) - min(args)。另一方面,如果没有传入参数,len(args)0,即假值,将返回0

请注意,编写此函数的另一种替代方式是:

def foo(*args):
    if not len(args):
        return 0
    
    return max(args) - min(args)

更简洁的说,

def foo(*args):
    return 0 if not args else max(args) - min(args)

当然,这些函数都不会执行任何类型检查,因此除非您完全信任所提供的输入,否则不要依赖这些结构的简单性。


or如何工作

我用一个假例子来解释or的工作原理。

给定一个定义为

def foo(*args):
    ...

你如何完成foo以返回所有大于9000的数字?

这里使用 or 来处理边角情况。我们将 foo 定义为:

def foo(*args):
     return [x for x in args if x > 9000] or 'No number over 9000!'

foo(9004, 1, 2, 500)
# [9004]

foo(1, 2, 3, 4)
# 'No number over 9000!'

foo 对列表进行过滤,以保留所有大于 9000 的数字。如果存在这样的数字,则列表推导式的结果为非空列表,其为真值,因此返回该列表(短路行为在此处生效)。如果不存在这样的数字,则列表推导式的结果为 [],其为假值。因此,将评估第二个表达式(一个非空字符串),并返回它。

使用条件语句,我们可以将这个函数重写为:

def foo(*args):
    r = [x for x in args if x > 9000]
    if not r:
        return 'No number over 9000!' 
    
    return r

与以前一样,这个结构在错误处理方面更加灵活。


41
在这里,为了简洁而牺牲所有的清晰度不符合“Pythonic”(Python风格),我认为这是情况。这不是一个直截了当的结构。 - DBedrenko
16
我认为应该注意到Python条件表达式使得这种语法不太常见了。如果参数长度为零,我肯定更喜欢max(args) - min(args)而不是原来的写法。 - richardb
4
另一个常见的令人困惑的例子是在没有值的情况下分配一个值:“some_var = arg or 3”。 - Erik
14
在人们开始抨击这种语法以支持三元运算符之前,请记住,当涉及到n元条件表达式时,三元运算符很快就会失控。例如,if ... else (if ... else (if ... else (if ... else ...)))同样可以重写为... and ... and ... and ... and ...,此时真的很难为任何一种情况争辩可读性。 - cs95
4
为了简短而牺牲清晰度并不符合 Pythonic 的规范,但这个例子并没有这样做。它是一个众所周知的习语。像其他习语一样,你必须学会它,但这并不代表“牺牲了清晰度”。 - Miles Rout
显示剩余10条评论

21

引用自 Python Docs

请注意,andor都不会将返回的类型限制为FalseTrue,而是返回最后评估的参数。这有时很有用,例如,如果s是应该在为空时替换为默认值的字符串,则表达式s or 'foo'会产生所需的值。

因此,这就是Python设计用于评估布尔表达式的方式,上述文档为我们提供了他们这样做的见解。

要获取布尔值,请将其转换为类型。

return bool(len(args) and max(args)-min(args))

为什么?

短路计算。

例如:

2 and 3 # Returns 3 because 2 is Truthy so it has to check 3 too
0 and 3 # Returns 0 because 0 is Falsey and there's no need to check 3 at all

同样的规则也适用于 or,即只要找到一个 Truthy 表达式,它就会返回该表达式,因为评估其余的表达式是多余的。
Python 返回的不是纯粹的 TrueFalse,而是 TruthyFalsey,无论如何都将评估为 TrueFalse。您可以直接使用该表达式,它仍然可以正常工作。

要了解什么是真值假值,请查看Patrick Haugh的答案


8

andor 是布尔逻辑运算符,但它们在比较时返回实际的值之一。使用 and 时,从左到右按布尔上下文评估值。0、''、[]、()、{} 和 None 在布尔上下文中均为假;其他所有值均为真。

如果在布尔上下文中所有值都为真,则 and 返回最后一个值。

>>> 2 and 5
5
>>> 2 and 5 and 10
10

如果在布尔上下文中有任何值为false,and运算符将返回第一个false值。
>>> '' and 5
''
>>> 2 and 0 and 5
0

因此,这段代码
return len(args) and max(args)-min(args)

当存在args时,返回max(args)-min(args)的值,否则返回0,即len(args)


5

这是合法/可靠的风格,还是有什么陷阱吗?

这是合法的,它是一种短路求值,返回最后一个值。

您提供了一个很好的例子。如果没有传递参数,则函数将返回0,代码不必检查未传递参数的特殊情况。

另一种使用方法是将None参数默认为可变原始类型,例如空列表:

def fn(alist=None):
    alist = alist or []
    ....

如果alist传递了一些非真实值,它将默认为空列表,这是一个方便的方法来避免if语句和可变默认参数陷阱

3

注意事项

是的,有一些需要注意的地方。

fn() == fn(3) == fn(4, 4)

首先,如果fn返回0,你无法知道它是没有带参数调用,还是带了一个参数或多个相同参数的调用:

>>> fn()
0
>>> fn(3)
0
>>> fn(3, 3, 3)
0

fn是什么意思?

Python是一种动态语言,没有明确规定fn的功能、输入和输出。因此,正确命名函数非常重要。同样,参数不必称为args。使用delta(*numbers)calculate_range(*numbers)可能更好地描述函数的目的。

参数错误

最后,逻辑运算符and用于防止在没有任何参数的情况下调用函数失败。但是,如果某个参数不是数字,则仍会失败:

>>> fn('1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'
>>> fn(1, '2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: '>' not supported between instances of 'str' and 'int'
>>> fn('a', 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'

可能的替代方案

以下是一种根据"先行执行,后请求原谅"原则编写函数的方法:

def delta(*numbers):
    try:
        return max(numbers) - min(numbers)
    except TypeError:
        raise ValueError("delta should only be called with numerical arguments") from None
    except ValueError:
        raise ValueError("delta should be called with at least one numerical argument") from None

作为示例:
>>> delta()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in delta
ValueError: delta should be called with at least one numerical argument
>>> delta(3)
0
>>> delta('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta(3, 4.5)
1.5
>>> delta(3, 5, 7, 2)
5

如果您真的不想在调用delta时没有任何参数时引发异常,您可以返回一些不可能出现的值(例如-1None):
>>> def delta(*numbers):
...     try:
...         return max(numbers) - min(numbers)
...     except TypeError:
...         raise ValueError("delta should only be called with numerical arguments") from None
...     except ValueError:
...         return -1 # or None
... 
>>> 
>>> delta()
-1

0

以简单的方式理解:

并且:如果 first_val 为 False,则返回 first_val,否则返回 second_value

例如:

1 and 2 # here it will return 2 because 1 is not False

但是,

0 and 2 # will return 0 because first value is 0 i.e False

and => 如果任意一个为假,结果就为假。只有两个都为真时,结果才为真。

OR : 如果 first_val 为 False,则返回 second_val,否则返回 first_value

原因是,如果第一个条件为假,则检查第二个条件是否为真。

例如:

1 or 2 # here it will return 1 because 1 is not False

但是,

0 or 2 # will return 2 because first value is 0 i.e False

或者 => 如果任何一个值为假,结果将为真。因此,如果第一个值为假,无论第二个值是什么,它都会返回第二个值。

如果任何一个值为真,则结果将为真。如果两个值都为假,则结果将为假。


0

是的。这是“and”比较的正确行为。

至少在Python中,如果A本质上是True(包括A不是Null、不是None、不是空容器(如空list、dict等)),则“A and B”返回B。只有当A本质上是False或None或Empty或Null时才返回A。

另一方面,“A or B”返回A,如果A本质上是True(包括A不是Null、不是None、不是空容器(如空list、dict等)),否则返回B。

很容易忽略或忽视这种行为,因为在Python中,任何非Null非空对象都被视为布尔值True。

例如,以下所有内容都将打印“True”

if [102]: 
    print "True"
else: 
    print "False"

if "anything that is not empty or None": 
    print "True"
else: 
    print "False"

if {1, 2, 3}: 
    print "True"
else: 
    print "False"

另一方面,以下所有内容都将打印“False”

if []: 
    print "True"
else: 
    print "False"

if "": 
    print "True"
else: 
    print "False"

if set ([]): 
    print "True"
else: 
    print "False"

谢谢。我想写的是A本质上是True。已更正。 - emmanuelsa

0
这是合法/可靠的风格,还是有什么陷阱吗? 我想补充一下这个问题,它不仅合法可靠而且也非常实用。这里有一个简单的例子:
>>>example_list = []
>>>print example_list or 'empty list'
empty list

因此,您可以真正利用它的优势。为了简洁明了,这是我对它的看法:

运算符

Python的or运算符返回第一个真值或最后一个值,并停止。

运算符

Python的and运算符返回第一个假值或最后一个值,并停止。

幕后花絮

在Python中,除了0之外,所有数字都被解释为True。因此,说:

0 and 10 

等同于:

False and True

这显然是False。因此,它返回0是合乎逻辑的。


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