(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,2
max = (a if a > b else b)
max = (a > b and [a] or [b])[0] #list
max = (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中對Null 字元串的布爾求值為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]永遠都是Null 字元串(在2.5上測試)。這是我比較faint的一點,為啥[""]為真而("")為假呢?
最後,附上python對典型數值的布爾求值結果,這對我們書寫三元運算的等價語句很有用。
輸入 |
布爾求值 |
1,-1,[“”] |
True |
0, “”, None, [], (), {}, (“”) |
False
|
python 三元運算式
之前學習的Python提到了對於類似C語言的三元條件運算式condition ? true_part : false_part,雖然Python沒有三目運算子(?:),但也有類似的替代方案,那就是true_part if condition else false_part。
複製代碼 代碼如下:
>>> 1 if True else 0
1
>>> 1 if False else 0
0
>>> "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 0
1
>>> False and 1 or 0
0
>>> True and "Fire" or "Water"
'Fire'
>>> False and "Fire" or "Water"
'Water'
但是值得注意的是雖然表面看上去能夠正常工作,其實還潛藏有不可知的風險,若我們的true_part本身就是個被Python認定為False的值,這個技巧就不可用了,我們知道Null 字元串就是這種情況。
複製代碼 代碼如下:
>>> True and "" or "Water"
'Water'
上面的運算式其實我們期望返回空字串的,如何解決呢,我在Dive Into Python中找到瞭解決方案:那就是利用列表特性,因為包含Null 字元串的列表其運算式值仍然為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》。