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

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

我正在尝试学习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。

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

I’m trying to learn python and came across some code that is nice and short but doesn’t totally make sense

the context was:

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

I get what it’s doing, but why does python do this – ie return the value rather than True/False?

10 and 7-2

returns 5. Similarly, changing the and to or will result in a change in functionality. So

10 or 7 - 2

Would return 10.

Is this legit/reliable style, or are there any gotchas on this?


回答 0

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

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

TL;DR

We start by summarising the two behaviour of the two logical operators and and or. These idioms will form the basis of our discussion below.

and

Return the first Falsy value if there are any, else return the last value in the expression.

or

Return the first Truthy value if there are any, else return the last value in the expression.

The behaviour is also summarised in the docs, especially in this table:

The only operator returning a boolean value regardless of its operands is the not operator.


“Truthiness”, and “Truthy” Evaluations

The statement

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

Is a very pythonic concise (and arguably less readable) way of saying “if args is not empty, return the result of max(args) - min(args)“, otherwise return 0. In general, it is a more concise representation of an if-else expression. For example,

exp1 and exp2

Should (roughly) translate to:

r1 = exp1
if r1:
    r1 = exp2

Or, equivalently,

r1 = exp2 if exp1 else exp1

Similarly,

exp1 or exp2

Should (roughly) translate to:

r1 = exp1
if not r1:
    r1 = exp2

Or, equivalently,

r1 = exp1 if exp1 else exp2

Where exp1 and exp2 are arbitrary python objects, or expressions that return some object. The key to understanding the uses of the logical and and or operators here is understanding that they are not restricted to operating on, or returning boolean values. Any object with a truthiness value can be tested here. This includes int, str, list, dict, tuple, set, NoneType, and user defined objects. Short circuiting rules still apply as well.

But what is truthiness?
It refers to how objects are evaluated when used in conditional expressions. @Patrick Haugh summarises truthiness nicely in this post.

All values are considered “truthy” except for the following, which are “falsy”:

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] – an empty list
  • {} – an empty dict
  • () – an empty tuple
  • '' – an empty str
  • b'' – an empty bytes
  • set() – an empty set
  • an empty range, like range(0)
  • objects for which
    • obj.__bool__() returns False
    • obj.__len__() returns 0

A “truthy” value will satisfy the check performed by if or while statements. We use “truthy” and “falsy” to differentiate from the bool values True and False.


How and Works

We build on OP’s question as a segue into a discussion on how these operators in these instances.

Given a function with the definition

def foo(*args):
    ...

How do I return the difference between the minimum and maximum value in a list of zero or more arguments?

Finding the minimum and maximum is easy (use the inbuilt functions!). The only snag here is appropriately handling the corner case where the argument list could be empty (for example, calling foo()). We can do both in a single line thanks to the and operator:

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

foo()
# 0

Since and is used, the second expression must also be evaluated if the first is True. Note that, if the first expression is evaluated to be truthy, the return value is always the result of the second expression. If the first expression is evaluated to be Falsy, then the result returned is the result of the first expression.

In the function above, If foo receives one or more arguments, len(args) is greater than 0 (a positive number), so the result returned is max(args) - min(args). OTOH, if no arguments are passed, len(args) is 0 which is Falsy, and 0 is returned.

Note that an alternative way to write this function would be:

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

Or, more concisely,

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

If course, none of these functions perform any type checking, so unless you completely trust the input provided, do not rely on the simplicity of these constructs.


How or Works

I explain the working of or in a similar fashion with a contrived example.

Given a function with the definition

def foo(*args):
    ...

How would you complete foo to return all numbers over 9000?

We use or to handle the corner case here. We define foo as:

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 performs a filtration on the list to retain all numbers over 9000. If there exist any such numbers, the result of the list comprehension is a non-empty list which is Truthy, so it is returned (short circuiting in action here). If there exist no such numbers, then the result of the list comp is [] which is Falsy. So the second expression is now evaluated (a non-empty string) and is returned.

Using conditionals, we could re-write this function as,

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

As before, this structure is more flexible in terms of error handling.


回答 1

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,检查帕特里克·哈夫的答案

Quoting from Python Docs

Note that neither and nor or restrict the value and type they return to False and True, but rather return the last evaluated argument. This is sometimes useful, e.g., if s is a string that should be replaced by a default value if it is empty, the expression s or 'foo' yields the desired value.

So, this is how Python was designed to evaluate the boolean expressions and the above documentation gives us an insight of why they did it so.

To get a boolean value just typecast it.

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

Why?

Short-circuiting.

For example:

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

The same goes for or too, that is, it will return the expression which is Truthy as soon as it finds it, cause evaluating the rest of the expression is redundant.

Instead of returning hardcore True or False, Python returns Truthy or Falsey, which are anyway going to evaluate to True or False. You could use the expression as is, and it will still work.


To know what’s Truthy and Falsey, check Patrick Haugh’s answer


回答 2

执行布尔逻辑,但它们在比较时返回实际值之一。使用和时,值在布尔上下文中从左到右求值。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。

and and or perform boolean logic, but they return one of the actual values when they are comparing. When using and, values are evaluated in a boolean context from left to right. 0, ”, [], (), {}, and None are false in a boolean context; everything else is true.

If all values are true in a boolean context, and returns the last value.

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

If any value is false in a boolean context and returns the first false value.

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

So the code

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

returns the value of max(args)-min(args) when there is args else it returns len(args) which is 0.


回答 3

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

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

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

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

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

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

Is this legit/reliable style, or are there any gotchas on this?

This is legit, it is a short circuit evaluation where the last value is returned.

You provide a good example. The function will return 0 if no arguments are passed, and the code doesn’t have to check for a special case of no arguments passed.

Another way to use this, is to default None arguments to a mutable primitive, like an empty list:

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

If some non-truthy value is passed to alist it defaults to an empty list, handy way to avoid an if statement and the mutable default argument pitfall


回答 4

陷阱

是的,有一些陷阱。

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

Gotchas

Yes, there are a few gotchas.

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

First, if fn returns 0, you cannot know if it was called without any parameter, with one parameter or with multiple, equal parameters :

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

What does fn mean?

Then, Python is a dynamic language. It’s not specified anywhere what fn does, what its input should be and what its output should look like. Therefore, it’s really important to name the function correctly. Similarly, arguments don’t have to be called args. delta(*numbers) or calculate_range(*numbers) might describe better what the function is supposed to do.

Argument errors

Finally, the logical and operator is supposed to prevent the function to fail if called without any argument. It still fails if some argument isn’t a number, though:

>>> 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'

Possible alternative

Here’s a way to write the function according to the “Easier to ask for forgiveness than permission.” principle:

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

As an example:

>>> 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

If you really don’t want to raise an exception when delta is called without any argument, you could return some value which cannot be possible otherwise (e.g. -1 or None):

>>> 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

回答 5

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

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

>>>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

Is this legit/reliable style, or are there any gotchas on this?

I would like to add to this question that it not only legit and reliable but it also ultra practical. Here is a simple example:

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

Therefore you can really use it at your advantage. In order to be conscise this is how I see it:

Or operator

Python’s or operator returns the first Truth-y value, or the last value, and stops

And operator

Python’s and operator returns the first False-y value, or the last value, and stops

Behind the scenes

In python, all numbers are interpreted as True except for 0. Therefore, saying:

0 and 10 

is the same as:

False and True

Which is clearly False. It is therefore logical that it returns 0


回答 6

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

至少在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"

Yes. This is the correct behaviour of and comparison.

At least in Python, A and B returns B if A is essentially True including if A is NOT Null, NOT None NOT an Empty container (such as an empty list, dict, etc). A is returned IFF A is essentially False or None or Empty or Null.

On the other hand, A or B returns A if A is essentially True including if A is NOT Null, NOT None NOT an Empty container (such as an empty list, dict, etc), otherwise it returns B.

It is easy to not notice (or to overlook) this behaviour because, in Python, any non-null non-empty object evaluates to True is treated like a boolean.

For example, all the following will print “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"

On the other hand, all the following will print “False”

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

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

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

回答 7

以简单的方式理解

与: 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值。因此它会返回第二个值。

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

to understand in simple way,

AND : if first_val is False return first_val else second_value

eg:

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

but,

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

and => if anyone false, it will be false. if both are true then only it will become true

OR : if first_val is False return second_val else first_value

reason is, if first is false it check whether 2 is true or not.

eg:

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

but,

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

or => if anyone false, it will be true. so if first value is false no matter what 2 value suppose to be. so it returns second value what ever it can be.

if anyone is true then it will become true. if both are false then it will become false.