(1) variable = a if exper else b(2)variable = (exper and [b] or [c])[0](2) variable = exper and b or c
上面三种用法都可以达到目的,类似c语言中 variable = exper ? b : c;即:如果exper表达式的值为true则variable = b,否则,variable = c
例如:
代码如下:
a,b=1,2max = (a if a > b else b)max = (a > b and [a] or [b])[0] #listmax = (a > b and a or b)
现在大部分高级语言都支持“?”这个三元运算符(ternary operator),它对应的表达式如下:condition ? value if true : value if false。很奇怪的是,这么常用的运算符python居然不支持!诚然,我们可以通过if-else语句表达,但是本来一行代码可以完成的非要多行,明显不够简洁。没关系,在python里其实还是有对应的表达方式的。 举个例子:char *ret = (x!=0) ? “true” : “false”这行代码对应的python形式就是ret = (x and “true”) or “false”(很简单吧,事实上括号可以去掉)。运行时,python虚拟机会对赋值符右边的布尔表达式(注意这里并非三元表达式)求值,返回值是最后一个被分析到的值。为什么是“最后一个被分析到的”而不是表达式中“最后一个”呢?因为布尔表达式有一个短路效应,比如a or b,如果a为真那么就不会分析b了。嗯,估计现在大家差不多明白了这行python代码的原理了。如果x为真,由于字符串“true”也为真,于是返回”true”,反之,x为假,那么就没必要看字符串”true”了(短路效应),直接返回”false”。 不难看出,三元运算在python中事实上可以通过借用布尔求值表达。然后,有时会有点小问题。举个例子,char *ret = x ? “” or “val”。根据前面的例子,我们很自然想到在python里应该这样写,ret = x and “” or “val”。错了!不管x的布尔求值是真还是假,ret得到的总是”val”。奇怪么?不奇怪,因为在python中对空字符串的布尔求值为false,这样x and “”永远都是false,所以ret得到的自然总是”val”了。解决这个问题有两种办法,第一种,也是我喜欢的一种,就是写成ret = not x and “val” or “”。第二种,麻烦一点ret=x and [“”] or [“val”],然后每次取ret[0]作为返回值,这是因为[“”]在布尔求值时值为true。 讨论一:第一种方法代码明显要简洁,效率也高,那么还有必要使用第二种么?当然,第一种办法有局限性,只有当我们非常明确其中一个值布尔求值时不可能为false时才能使用。在我们的示例中,由于”val”肯定返回true所以可以使用。如果是两个变量呢,像这样ret=x and val1 or val2,你就只能老老实实写成ret=x and [val1] or [val2],然后取ret[0]作为结果了。因为这行语句所表达的不是“当x为真返回val1,否则返回val2”,而是“当x为真并且val1为真返回val2,否则返回val2”。 讨论二:大家都知道python里有list和tuple,前面这行代码ret=x and [“”] or [“val”]我们就是通过list解决,有的人可能偏爱tuple,于是就会这样写ret=x and (“”) or (“val”)。错了!这里ret[0]永远都是空字符串(在2.5上测试)。这是我比较faint的一点,为啥[“”]为真而(“”)为假呢? 最后,附上python对典型数值的布尔求值结果,这对我们书写三元运算的等价语句很有用。
输入
布尔求值
代码如下:
>>> 1 if true else 01>>> 1 if false else 00>>> “fire” if true else “water”‘fire’>>> “fire” if false else “water”‘water’
在编程中我也一直这么用了,直到有一天发现了一个有趣的技巧,那就是and-or技巧,利用条件判断的优先特性来实现三元条件判断,比如p∧q,在python中如果p为假,那么python将不会继续执行q,而直接判定整个表达式为假(p值),当然如果p为真,那就还要继续执行q来决定整个表达式值;同样的p∨q,如果p为真,那么就不会继续执行q了…
其实很多编程语言在逻辑判断中都应用了这套机制,目前我接触下来的貌似vb/vbscript可能不是这么做的。有了这套机制除了在if判断中提高效率外,我们还可以额外发掘一些有趣的功能,比如下面的php代码:
代码如下:
$conn = @mysql_connect(…) or die(“failed”)
如果mysql_connect成功的话将会返回resource资源句柄,如果失败的话将会返回false,等等,后面还有个or,也就是失败的话还将会继续执行or后面的die语句,于是输出了错误信息并终止后续代码的执行。
再如下面的javascript代码:
代码如下:
function getevent(e) { e = e || window.event; return e;}
这段代码获取的是event,假如没有给getevent传入值(即e为undefined),或者e为null(两者在javascript条件中均代表false),e = e || window.event表达式将会把window.event赋值给e,否则e为object对象,原表达式会蜕化为e = e赋值,也就是没有改变什么。
好了,扯了这么多,稍稍有些偏题了,下面继续聊python的and-or技巧,可以这么说,这个技巧也是利用了逻辑判断的特殊性,貌似在真正的三元表达式if else没有出来的时候其就一直在扮演三元表达式的角色,其原型是condition and true_part or false_part,下面举几个例子:
代码如下:
>>> true and 1 or 01>>> false and 1 or 00>>> true and “fire” or “water”‘fire’>>> false and “fire” or “water”‘water’
但是值得注意的是虽然表面看上去能够正常工作,其实还潜藏有不可知的风险,若我们的true_part本身就是个被python认定为false的值,这个技巧就不可用了,我们知道空字符串就是这种情况。
代码如下:
>>> true and “” or “water”‘water’
上面的表达式其实我们期望返回空字串的,如何解决呢,我在pe into python中找到了解决方案:那就是利用列表特性,因为包含空字符串的列表其表达式值仍然为true,所以我们可以用列表先包装一下,然后等表达式判断完毕后在解包:
代码如下:
>>> a = “”>>> b = “water”>>> (true and [a] or [b])[0]”
当然为了避免出错,我们可以将其包装为函数:
代码如下:
def iif(condition, true_part, false_part): return (condition and [true_part] or [false_part])[0]
现在python已经在语言特性中加入三元条件表达式的支持了,那就是文章一开始介绍的if else写法,所以为了妥善起见,对于三元判断还是用新的if else特性吧,其实python官方对于加入三元表达式语法也是讨论了很久的,可以参考《pep 308 — conditional expressions》。