“和”和“或”如何与非布尔值一起使用?


97

我正在尝试学习python,并遇到了一些不错的代码,虽然简短但并不完全有意义

上下文是:

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

我知道它在做什么,但是python为什么要这样做-即返回值而不是True / False?

10 and 7-2

返回5。同样,将和更改为或将导致功能更改。所以

10 or 7 - 2

将返回10。

这是合法/可靠的风格,还是有任何陷阱?


1
and(以及or)不限于使用布尔值或返回布尔值。
cs95

1
恕我直言:这是一种令人困惑的书写方式;我不能随便告诉它是否应该返回布尔值(是否有明显的最小和最大)或数字(最小和最大的差是多少)。如果是后者,那么还存在一个问题,将零长度列表的差异作为数字是否有意义。(而不是None例外)
ilkkachu

7
正如其他人所解释的那样,它起作用,但是一个可能的问题是,如果返回0,则无法确定args是空还是非空,但所有元素都相等。
特别是石灰,

@EspeciallyLime:完全是。我在回答中提到了它。
艾里克·杜米尼尔

Answers:


135

TL; DR

我们首先总结两个逻辑运算符and和的两个行为or。这些习语将构成我们下面讨论的基础。

and

如果有,则返回第一个Falsy值,否则返回表达式中的最后一个值。

or

如果有,则返回第一个Truthy值,否则返回表达式中的最后一个值。

行为也总结在docs中,尤其是在此表中:

在此处输入图片说明

无论其操作数如何,返回布尔值的唯一运算符是该not运算符。


“真实性”和“真实”评估

该声明

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

是一种非常 Python式的简洁(并且可能可读性较低)的说法,即“如果args不为空,则返回结果max(args) - min(args)”,否则返回0。通常,它是if-else表达式的更简洁表示。例如,

exp1 and exp2

应该(大致)翻译为:

r1 = exp1
if r1:
    r1 = exp2

或者,等效地,

r1 = exp1 if exp1 else exp2

同样,

exp1 or exp2

相当于

r1 = exp1
if not r1:
    r1 = exp2

其中exp1exp2是任意的python对象,或返回某些对象的表达式。在这里理解逻辑andor运算符用法的关键是要理解它们不限于对布尔值进行操作或返回布尔值。任何具有真实性值的对象都可以在此处进行测试。这包括intstrlistdicttuplesetNoneType,和用户定义的对象。短路规则也同样适用。

但是什么是真实性?
它指的是在条件表达式中使用对象时如何求值。@Patrick Haugh在这篇文章中很好地总结了真实性。

除以下值“ falsy”外,所有值均被视为“真实”值:

  • 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。请注意,如果第一个表达式的值为真,则返回值始终第二个表达式的结果。如果第一个表达式的计算结果为Falsy,则返回的结果为第一个表达式的结果。

在上面的函数中,如果foo接收到一个或多个参数,len(args)则大于0(一个正数),因此返回的结果为max(args) - min(args)。OTOH,如果没有参数传递,len(args)0这是Falsy,并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。如果存在任何这样的数字,则列表理解的结果是一个非空列表,该列表为Truthy,因此将其返回(此处发生短路)。如果不存在这样的数字,则list comp的结果[]为Falsy。因此,现在对第二个表达式求值(一个非空字符串)并返回。

使用条件,我们可以将该函数重写为:

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

    return r

和以前一样,此结构在错误处理方面更加灵活。


33
为了简洁而牺牲所有清晰度并不是“ pythonic”的,我认为在这种情况下就是如此。这不是一个简单的构造。
DBedrenko

11
我认为应该注意到,Python条件表达式已使这种语法变得不那么普遍了。我当然更喜欢max(args)-min(args)如果len(args)否则为0。
richardb

3
一开始令人困惑的另一个常见问题是,如果不存在则分配一个值:“ some_var = arg或3”
Erik

12
@Baldrickk在人们开始抨击此语法以支持三元运算符之前,请记住,当涉及n元条件表达式时,三元运算符会很快失控。例如,if ... else (if ... else (if ... else (if ... else ...)))可以很好地重写它... and ... and ... and ... and ...,到那时,无论哪种情况都很难争论可读性。
cs95

4
为了简洁而牺牲清晰度不是Python的目的,但这不是这样做的。这是一个众所周知的习语。就像任何其他习语一样,这是一个必须学习的习语,但这几乎没有“牺牲清晰度”。
Miles Rout

18

Python Docs报价

注意,既不and也不or 限制它们返回到和的类型,而是返回最后一个求值的参数。有时这很有用,例如,如果为空则应替换为默认值的字符串,则表达式会产生所需的值。FalseTruess 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表达式,因为评估表达式的其余部分是多余的。

而不是返回铁杆True或者False,Python的回报TruthyFalsey,这是无论如何要评估为TrueFalse。您可以按原样使用该表达式,它将仍然有效。


要知道什么是TruthyFalsey,检查帕特里克·哈夫的答案


7

执行布尔逻辑,但它们在比较时返回实际值之一。使用和时,值在布尔上下文中从左到右求值。0,'',[],(),{}None在布尔上下文中为false;其他一切都是真的。

如果所有的值在布尔上下文是真实的,并且返回最后一个值。

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

如果任何值在布尔上下文中为false ,则返回第一个false值。

>>> '' and 5
''
>>> 2 and 0 and 5
0

所以代码

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

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


5

这是合法/可靠的风格,还是有任何陷阱?

这是合法的,这是短路评估,返回最后一个值。

您提供了一个很好的例子。0如果不传递任何参数,该函数将返回,并且代码不必检查是否传递无参数的特殊情况。

使用此方法的另一种方法是将None参数默认设置为可变基元,例如空列表:

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

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


3

陷阱

是的,有一些陷阱。

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

首先,如果fnreturn 0,则无法知道是否在没有任何参数,一个参数或多个相等参数的情况下调用它:

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

什么fn意思

那么,Python是一种动态语言。在任何地方都没有指定它的fn作用,输入应为什么以及输出应为什么样。因此,正确命名函数真的很重要。同样,不必调用参数argsdelta(*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

这是合法/可靠的风格,还是有任何陷阱?

我想补充一下这个问题,它不仅合法可靠,而且非常实用。这是一个简单的示例:

>>>example_list = []
>>>print example_list or 'empty list'
empty list

因此,您可以真正利用它来发挥自己的优势。为了简明扼要,这是我的看法:

Or 算子

Python的or运算符返回第一个Truth-y值或最后一个值,然后停止

And 算子

Python的and运算符返回第一个False-y值或最后一个值,然后停止

幕后花絮

在python中,所有数字都被解释True为0以外的数字。因此,请说:

0 and 10 

是相同的:

False and True

这显然是False。因此,逻辑上返回0


0

是。这是和比较的正确行为。

至少在Python,A and B返回B如果A基本上True包括:如果A不为空,NOT None不是空的容器(如一个空的listdict等)。A返回的IFF A本质上是Falseor None或Empty或Null。

在另一方面,A or B返回A如果A基本上True包括:如果A不为空,NOT None不是空的容器(如一个空的listdict等),否则返回B

不容易注意到(或忽略)此行为,因为在Python中,任何non-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

以简单的方式理解

与: if first_val is False return first_val else 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

=>如果任何人为假,它将为假。如果两个都成立,那么只有它会成立

要么 : if first_val is False return second_val else first_value

原因是,如果first为false,则检查2是否为true。

例如:

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

或=>如果任何人为假,则为true。因此,如果第一个值是假,那么无论假设哪个是2值。因此它会返回第二个值。

如果任何人是真实的,那么它将成为真实。如果两者均为假,那么它将变为假。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.