奇怪的使用python的'和'运算符

我试图学习python,碰到一些代码很好但很短,但并不完全合理

背景是:

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

我得到它在做什么,但为什么python这样做 – 即返回值而不是真/假?

 10 and 7-2 

返回5。

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

TL; DR

逻辑ANDand ):

如果有的话返回第一个Falsey值,否则返回expression式中的最后一个值。

逻辑or ):

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


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

是一个非常 pythonic简洁的方式说:

如果args不为空,则返回max(args) - min(args)

逻辑and运算符不限于使用或返回布尔值。 任何具有真实值的对象都可以在这里进行testing。 这包括intstrlistdicttuplesetNoneType和用户定义的对象。 短路规则仍然适用。

请注意,这是构buildif-elseexpression式的更简洁的方法:

 return exp1 and exp2 

应该(大致)转化为:

 r1 = exp1 if not r1: return r1 return exp2 

这里有一些例子说明这些结构如何以及在哪里可以用来简洁地处理无效input。

示例1 (如OP所示)

返回一组参数的minmax之间的差值。

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

由于and被使用,第二个expression式也必须被评估,如果第一个是True 。 请注意,如果第一个expression式被评估为Truthy,则返回的结果总是 第二个expression式的结果。

如果第一个expression式被评估为Falsey,则返回的结果是第一个expression式的结果。

如果foo收到任何参数, len(args)大于0 (一个正数),所以返回的结果是max(args) - min(args)

如果foo没有收到参数, len(args)0 ,这是Falsey,返回0

请注意,另一种写这个函数的方法是:

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

例2

返回9000以上的所有数字。

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

在这种情况下or被使用。 如果第一个expression式是Truthy,则返回的结果是第一个expression式的结果。 否则,将对两个expression式进行求值并返回结果是第二个expression式的结果。

foo在列表上执行过滤以保留超过9000所有数字。 如果存在这样的数字,则列表理解的结果是一个非空列表,即Truthy,所以它被返回(在这里短路)。

如果不存在这样的数字,那么列表comp的结果是[]是Falsey。 所以现在计算第二个expression式(一个非空string)并返回。

而这个function的替代scheme是:

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

从Python文档引用

请注意,既不and也不限制它们返回FalseTruetypes ,而是返回最后一次评估的参数 。 这有时是有用的,例如,如果s是一个string,如果它是空的,它应该被默认值replace,则expression式s or 'foo'产生所需的值。

所以,这就是Pythondevise来评估布尔expression式的方式,上面的文档让我们了解了他们为什么这样做。

为了得到一个布尔值,只是forms化它。

 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 

也是一样,也就是说,只要发现它,就会返回Truthy的expression式,因此评估其余的expression式是多余的。

Python不是返回TrueFalse核心,而是返回TruthyFalsey ,无论如何,它们将评估为TrueFalse 。 你可以使用这个expression式,它仍然可以工作。


要知道TruthyFalsey什么 ,请检查Patrick Haugh的答案

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

如果所有值在布尔上下文中都是true, 返回最后一个值。

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

当有args时返回max(args)-min(args)的值,否则返回len(args) ,它是0。

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

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

你提供了一个很好的例子。 如果没有parameter passing,函数将返回0 ,代码不需要检查没有parameter passing的特殊情况。

另一种使用这种方法的方法是将默认的None参数默认为一个可变原语,如空列表:

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

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

陷阱

是的,有几个陷阱。

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

首先,如果fn返回0 ,则无法知道是否使用一个参数或多个相同的参数调用该参数:

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

fn是什么意思?

那么,Python是一种dynamic语言。 它没有被指定到什么地方,它的input是什么,输出应该是什么样的。 因此,正确命名function是非常重要的。 同样,参数不必被称为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' 

可能的select

这里有一种方法可以根据“容易请求宽恕而不是允许”来编写函数。 原则 :

 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时引发exception,你可以返回一些不可能的值(例如-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 

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

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

 >>>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是合乎逻辑的

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

至less在Python中,如果A基本为True ,则A and B返回B ,包括如果A不是空,则不是非空容器(如空listdict等)。 A返回IFF A本质上是FalseNone或Empty或Null。

另一方面,如果A基本为True ,则A or B返回A ,包括如果A不是空,则不是非空容器(如空listdict等),否则返回B

很容易注意到(或忽略)这种行为,因为在Python中,任何non-null非空对象的求值为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" 

另一方面,所有以下将打印“假”

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