系列導(dǎo)讀 接著上篇繼續(xù)后面兩個(gè)章節(jié),函數(shù)和解析式。 Python 里函數(shù)太重要了 (說的好像在別的語言中函數(shù)不重要似的)。函數(shù)的通用好處就不用多說了吧,重復(fù)使用代碼,增強(qiáng)代碼可讀性等等。 還記得 Python 里面『萬物皆對(duì)象』么?Python 把函數(shù)也當(dāng)成對(duì)象,可以從另一個(gè)函數(shù)中返回出來而去構(gòu)建高階函數(shù),比如
4.1 正規(guī)函數(shù) Python 里面的正規(guī)函數(shù) (normal function) 就像其他語言的函數(shù)一樣,之所以說正規(guī)函數(shù)是因?yàn)檫€有些「不正規(guī)」的,比如匿名函數(shù),高階函數(shù)等等。 但即便是正規(guī)函數(shù),Python 的函數(shù)具有非常靈活多樣的參數(shù)形態(tài),既可以實(shí)現(xiàn)簡(jiǎn)單的調(diào)用,又可以傳入非常復(fù)雜的參數(shù)。從簡(jiǎn)到繁的參數(shù)形態(tài)如下:
每種參數(shù)形態(tài)都有自己對(duì)應(yīng)的應(yīng)用,接下來用定義一個(gè)金融產(chǎn)品為例來說明各種參數(shù)形態(tài)的具體用法。 先從最簡(jiǎn)單的「位置參數(shù)」開始介紹: 解釋一下函數(shù)里面的各個(gè)部分:
用金融產(chǎn)品舉例,每個(gè)產(chǎn)品都有自己的 ID,定義 instrument 函數(shù),它只有一個(gè)「位置參數(shù)」。 def instrument( id ): print( 'id:', id ) 給 id 賦值 'MM1001' 并運(yùn)行該函數(shù),得到
id: MM1001 當(dāng)然「位置參數(shù)」可以是多個(gè),比如 id 和 ntl (代表 notional,本金,例如債券的本金是一億,期權(quán)的本金是一百萬等等):
給 ntl 賦值 100000 并運(yùn)行該函數(shù),得到 instrument1( 'MM1001', 1000000 )
如果你沒有給 ntl 賦值,程序會(huì)報(bào)錯(cuò) instrument1( 'MM1001' )
怎么破?來看看「默認(rèn)參數(shù)」。 解釋一下函數(shù)里面的各個(gè)部分 (黃色高亮的是新內(nèi)容):
在對(duì)金融產(chǎn)品估值時(shí),通常對(duì)一個(gè)單位的產(chǎn)品先估值,再乘以產(chǎn)品具體的本金。比如 1 美元的債券現(xiàn)值為 0.98 美元,那么 1 億美元的債券現(xiàn)值為 98,000,000 美元。 因此我們將 ntl 設(shè)為「默認(rèn)參數(shù)」,并設(shè)定一個(gè)默認(rèn)值 1。 def instrument2( id, ntl=1 ): print( 'id:', id ) print( 'notional:', ntl ) 這時(shí)調(diào)用 instrument2 不給 ntl 賦值也可以運(yùn)行,ntl 就取其默認(rèn)值 1。
id: MM1001 當(dāng)然你可以把 ntl 像「位置參數(shù)」那樣對(duì)待,給它設(shè)定任何值
id: MM1001 使用「默認(rèn)參數(shù)」最大的好處是能降低調(diào)用函數(shù)的難度,不過有個(gè)知識(shí)點(diǎn)需要注意。
SyntaxError: non-default argument 當(dāng)然「默認(rèn)參數(shù)」可以是多個(gè),比如 ntl 和 curR (報(bào)表貨幣,對(duì)于中國(guó)的銀行用 CNY)
假設(shè)我們先給 ntl 和 curR 賦值 100 和 'USD' instrument3( 'MM1001', 100, 'USD' )
但有時(shí)在調(diào)用函數(shù)時(shí),我們會(huì)記不住參數(shù)的順序。比如 ntl 和 curR 的位置寫反了 instrument3( 'MM1001', 'USD', 100 )
得到的結(jié)果毫無意義,那么記不住參數(shù)順序怎么破?在調(diào)用參數(shù)把它的「關(guān)鍵字」也帶上,我們就可以隨便調(diào)換參數(shù)的順序。 instrument3( 'MM1001', curR='USD', ntl=100 )
這樣怎么改變參數(shù)順序都可以打印出有意義的結(jié)果了。讀者可能會(huì)說了我也記不住「關(guān)鍵字」啊,是的,但是「關(guān)鍵字」看上去有具體的意義,絕對(duì)比你記住參數(shù)順序容易多了吧。 在 Python 函數(shù)中,還可以定義「可變參數(shù)」。顧名思義,可變參數(shù)就是傳入的參數(shù)個(gè)數(shù)是可變的,可以是 0, 1, 2 到任意個(gè)。 解釋一下函數(shù)里面的各個(gè)部分 (黃色高亮的是新內(nèi)容):
金融產(chǎn)品未來多個(gè)折現(xiàn)現(xiàn)金流 (discounted cash flow, DCF),但不知道具體多少個(gè),這時(shí)我們可以用 *args 來表示不確定個(gè)數(shù)的 DCF。下面程序也對(duì) DCF 加總得到產(chǎn)品現(xiàn)值 (present value, PV) def instrument4( id, ntl=1, curR='CNY', *args ): PV = 0 for n in args: PV = PV + n
print( 'id:', id ) print( 'notional:', ntl ) print( 'reporting currency:', curR ) print( 'present value:', PV*ntl ) 如果一個(gè)產(chǎn)品 (單位本金) 在后 3 年的折現(xiàn)現(xiàn)金流為 1, 2, 3,將它們傳入 *args,計(jì)算出它的現(xiàn)值為 600 = 100*(1+2+3)。
id: MM1001 除了直接傳入多個(gè)參數(shù)之外,還可以將所有參數(shù)先組裝成元組 DCF,用以「*DCF」的形式傳入函數(shù) (DCF 是個(gè)元組,前面加個(gè)通配符 * 是拆散元組,把元組的元素傳入函數(shù)中)
id: MM1001 可變參數(shù)用兩種方式傳入
解釋一下函數(shù)里面的各個(gè)部分 (黃色高亮的是新內(nèi)容):
「可變參數(shù)」和「關(guān)鍵字參數(shù)」的同異總結(jié)如下:
在定義金融產(chǎn)品,有可能不斷增加新的信息,比如交易對(duì)手、工作日慣例、工作日計(jì)數(shù)慣例等等。我們可以用「關(guān)鍵字參數(shù)」來滿足這種需求,即用 **kw。
如果不傳入任何「關(guān)鍵字參數(shù)」,kw 為空集。 instrument5( 'MM1001', 100, 'EUR', 1, 2, 3 )
當(dāng)知道交易對(duì)手 (counterparty) 是高盛時(shí),給函數(shù)傳入一個(gè)「關(guān)鍵字參數(shù)」,ctp = 'GS'。 instrument5( 'MM1001', 100, 'EUR', 1, 2, 3, ctp='GS' )
當(dāng)知道日期計(jì)數(shù) (daycount) 是 act/365 時(shí),再給函數(shù)傳入一個(gè)「關(guān)鍵字參數(shù)」,dc = 'act/365'。 instrument5( 'MM1001', 100, 'EUR', 1, 2, 3, dc='act/365', ctp='GS' )
除了直接傳入多個(gè)參數(shù)之外,還可以將所有參數(shù)先組裝成字典 Conv,用以「**Conv」的形式傳入函數(shù) (Conv 是個(gè)字典,前面加個(gè)通配符 ** 是拆散字典,把字典的鍵值對(duì)傳入函數(shù)中) DCF = (1, 2, 3, 4, 5) Conv = {'dc':'act/365', 'bdc':'following'} instrument5( 'MM1001', 10, 'EUR', *DCF, **Conv )
對(duì)于關(guān)鍵字參數(shù),函數(shù)的調(diào)用者可以傳入任意不受限制的關(guān)鍵字參數(shù)。 解釋一下函數(shù)里面的各個(gè)部分 (黃色高亮的是新內(nèi)容):
如果要限制關(guān)鍵字參數(shù)的名字,就可以用「命名關(guān)鍵字參數(shù)」,例如,用戶希望交易對(duì)手 ctp 是個(gè)關(guān)鍵字參數(shù)。這種方式定義的函數(shù)如下: def instrument6( id, ntl=1, curR='CNY', *, ctp, **kw ): print( 'id:', id ) print( 'notional:', ntl ) print( 'reporting currency:', curR ) print( 'counterparty:', ctp ) print( 'keyword:', kw) 從調(diào)用函數(shù) instrument6 得到的結(jié)果可看出 ctp 是「命名關(guān)鍵字參數(shù)」,而 dc 是「關(guān)鍵字參數(shù)」。
id: MM1001 使用命名關(guān)鍵字參數(shù)時(shí),要特別注意不能缺少參數(shù)名。下例沒有寫參數(shù)名 ctp,因此 'GS' 被當(dāng)成「位置參數(shù)」,而原函數(shù)只有 3 個(gè)位置函數(shù),現(xiàn)在調(diào)用了 4 個(gè),因此程序會(huì)報(bào)錯(cuò):
TypeError: instrument6() takes from 1 to 3 在 Python 中定義函數(shù),可以用位置參數(shù)、默認(rèn)參數(shù)、可變參數(shù)、命名關(guān)鍵字參數(shù)和關(guān)鍵字參數(shù),這 5 種參數(shù)中的 4 個(gè)都可以一起使用,但是注意,參數(shù)定義的順序必須是:
要注意定義可變參數(shù)和關(guān)鍵字參數(shù)的語法:
命名關(guān)鍵字參數(shù)是為了限制調(diào)用者可以傳入的參數(shù)名,同時(shí)可以提供默認(rèn)值。定義命名關(guān)鍵字參數(shù)不要忘了寫分隔符 *,否則定義的是位置參數(shù)。 警告:雖然可以組合多達(dá) 5 種參數(shù),但不要同時(shí)使用太多的組合,否則函數(shù)很難懂。 4.2 匿名函數(shù) 在 Python 里有兩種函數(shù)
匿名函數(shù) (anonymous function) 的說明如下: 解釋一下函數(shù)里面的各個(gè)部分:
注意 lambda 函數(shù)沒有所謂的函數(shù)名 (function_header),這也是它為什么叫匿名函數(shù)。下面是一些 lambda 函數(shù)示例: lambda x, y: x*y;函數(shù)輸入是 x 和 y,輸出是它們的積 x*y
6 lambda *args: sum(args);輸入是任意個(gè)數(shù)的參數(shù),輸出是它們的和
15 lambda **kwargs: 1;輸入是任意鍵值對(duì)參數(shù),輸出是 1
1 看個(gè)具體的平方函數(shù)例子:
<function __main__.<lambda>(x)> 這個(gè) lambda 函數(shù) lbd_sqr 做的事和下面正規(guī)函數(shù) sqr 做的一樣:
<function __main__.sqr(x)> 用實(shí)際結(jié)果來驗(yàn)證一下:
81 對(duì)于 lambda 函數(shù),有時(shí)我們會(huì)過用 (overuse) 它或誤用 (misuse) 它。 誤用情況:如果用 lambda 函數(shù)只是為了賦值給一個(gè)變量,用 def 的正規(guī)函數(shù)。 上面舉的例子就是反例,
<function <lambda> at 0x000001997AA721E0> 你看,lbd_sqr 的返回值是以 <lambda> 標(biāo)識(shí)的函數(shù),而 sqr 的返回時(shí)是以 sqr 為標(biāo)識(shí)的函數(shù),明顯后者一看就知道該函數(shù)是「計(jì)算平方」用的。 過用情況:如果一個(gè)函數(shù)很重要,它需要一個(gè)正規(guī)的名字。 有些人覺得 lambda 函數(shù)很酷,會(huì)不分場(chǎng)合瘋狂地用它。比如下面一個(gè)例子,根據(jù)「字符長(zhǎng)度」和「首個(gè)字母」來對(duì)列表來排序。
['Cyan', 'purple', 'Salmon', 'Goldenrod'] 在 sorted 函數(shù)有個(gè) key 的參數(shù),key 的值是排序的根據(jù)。比如上面用 lambda 函數(shù)設(shè)定字符長(zhǎng)度 len(c) 和忽略大小的首個(gè)字母 c.casefold(),c 表示具體的列表。 坦白的說,這樣用 lambda 函數(shù)看起來是很酷,但是增加了使用者的「思考成本」,用 def 顯性定義個(gè)函數(shù)可讀性會(huì)好很多。
['Cyan', 'purple', 'Salmon', 'Goldenrod'] 用正規(guī)函數(shù)還能加個(gè)函數(shù)說明 (docstring),再起個(gè)描述性強(qiáng)的函數(shù)名,讓人一看就知道該函數(shù)做什么。 4.3 高階函數(shù) 高階函數(shù) (high-order function) 在函數(shù)化編程 (functional programming) 很常見,主要有兩種形式:
Python 里面的 map, filter 和 reduce 屬于第一種高階函數(shù),參數(shù)是函數(shù)。這時(shí)候是不是很自然的就想起了 lambda 函數(shù)? 作為內(nèi)嵌在別的函數(shù)里的參數(shù),lambda 函數(shù)就像微信小程序一樣,即用即丟,非常輕便。 首先看看 map, filter 和 reduce 的語法:
看個(gè)具體的平方示例,用 map 函數(shù)對(duì)列表每個(gè)元素平方。
<map object at 0x0000018C83E72390> 在 map 函數(shù)中
注意 map_iter 是 map 函數(shù)的返回對(duì)象 (它是一個(gè)迭代器),想要將其內(nèi)容顯示出來,需要用 list 將其轉(zhuǎn)換成「列表」形式。有點(diǎn)奇怪是不是?為什么 map 函數(shù)不直接返回列表呢?看完下面「惰性求值」的知識(shí)點(diǎn)就明白了。 接著再看看 filter 函數(shù),顧名思義就是篩選函數(shù),那么我們把剛才列表中的計(jì)數(shù)篩選出來吧。
<filter object at 0x0000018C83E722E8> 在 filter 函數(shù)中
同樣,filter_iter 作為 filter 函數(shù)的返回對(duì)象,也是一個(gè)迭代器,想要將其內(nèi)容顯示出來,需要用 list 將其轉(zhuǎn)換成「列表」形式。 最后來看看 reduce 函數(shù),顧名思義就是累積函數(shù),把一組數(shù)減少 (reduce) 到一個(gè)數(shù)。
15 在 reduce 函數(shù)中
在 reduce 函數(shù)的第三個(gè)參數(shù)還可以賦予一個(gè)初始值,
115 這是累積從 100 和列表 lst = [1,2,3,4,5] 的第一個(gè)元素 1 開始,一直加到整個(gè) lst 元素遍歷完,因此最后求和為 115。 小結(jié)一下,對(duì)于 map, filter 和 reduce,好消息是,Python 支持這些基本的操作;而壞消息是,Python 不建議你使用它們。下節(jié)的「解析式」可以優(yōu)雅的替代 map 和 filter。 除了 Python 這些內(nèi)置函數(shù),我們也可以自己定義高階函數(shù),如下:
這個(gè) apply_to_list 函數(shù)和上面的 map, filter 和 reduce 的格式類型,第一個(gè)參數(shù) fun 是可以作用到列表的函數(shù),第二個(gè)參數(shù)是一個(gè)列表。下面代碼分別求出列表中所有元素的和、個(gè)數(shù)和均值。 lst = [1, 2, 3, 4, 5] print( apply_to_list( sum, lst ) ) print( apply_to_list( len, lst ) ) print( apply_to_list( lambda x:sum(x)/len(x), lst ) )
Python 里面的閉包 (closure) 屬于第二種高階函數(shù),返回值是函數(shù)。下面是一個(gè)閉包函數(shù)。 def make_counter(init): counter = [init] def inc(): counter[0] += 1 def dec(): counter[0] -= 1 def get(): return counter[0] def reset(): counter[0] = init return inc, dec, get, reset 此函數(shù)的作用是做一個(gè)計(jì)數(shù)器,可以
3 續(xù)了三秒。
2 減了一秒,相當(dāng)于續(xù)了兩秒。
0 重新計(jì)秒。 屬于第二類 (返回值是函數(shù)) 的高階函數(shù)還有「偏函數(shù)」和「柯里化」,由于它們比較特別,因此專門分兩節(jié)來講解。 4.4 偏函數(shù) 偏函數(shù) (paritial function) 主要是把一個(gè)函數(shù)的參數(shù) (一個(gè)或多個(gè)) 固定下來,用于專門的應(yīng)用上 (specialized application)。要用偏函數(shù)用從 functools 中導(dǎo)入 partial 包:
舉個(gè)排序列表里元素的例子 lst = [3, 1, 2, 5, 4] sorted( lst )
我們知道 sort 函數(shù)默認(rèn)是按升序排列,假設(shè)在你的應(yīng)用中是按降序排列,你可以把函數(shù)里的 reverse 參數(shù)設(shè)置為 True。 sorted( lst, reverse=True )
這樣每次設(shè)定參數(shù)很麻煩,你可以專門為「降序排列」的應(yīng)用定義一個(gè)函數(shù),比如叫 sorted_dec,用偏函數(shù) partial 把內(nèi)置的 sort 函數(shù)里的 reverse 固定住,代碼如下: sorted_dec = partial( sorted, reverse=True ) sorted_dec
不難發(fā)現(xiàn) sorted_dec 是一個(gè)函數(shù),而且參數(shù)設(shè)置符合我們的應(yīng)用,把該函數(shù)用到列表就能對(duì)于降序排列。 sorted_dec( lst )
小結(jié),當(dāng)函數(shù)的參數(shù)個(gè)數(shù)太多,需要簡(jiǎn)化時(shí),使用 functools.partial 可以創(chuàng)建一個(gè)新的函數(shù),即偏函數(shù),它可以固定住原函數(shù)的部分參數(shù),從而在調(diào)用時(shí)更簡(jiǎn)單。 4.5 柯里化 最簡(jiǎn)單的柯里化 (currying) 指的是將原來接收 2 個(gè)參數(shù)的函數(shù) f(x, y) 變成新的接收 1 個(gè)參數(shù)的函數(shù) g(x) 的過程,其中新函數(shù) g = f(y)。 以普通的加法函數(shù)為例: def add1(x, y): return x + y 通過嵌套函數(shù)可以把函數(shù) add1 轉(zhuǎn)換成柯里化函數(shù) add2。
仔細(xì)看看函數(shù) add1 和 add2 的參數(shù) (常數(shù)用紅色表示)
下面代碼也證實(shí)了上述分析: add1 add2 g = add2(2) g
比較「普通函數(shù) add1」和「柯里化函數(shù) add2」的調(diào)用,結(jié)果都一樣。 print( add1(2, 3) ) print( add2(2)(3) ) print( g(3) )
5.1 大框架 解析式 (comprehension) 是將一個(gè)可迭代對(duì)象轉(zhuǎn)換成另一個(gè)可迭代對(duì)象的工具。 上面出現(xiàn)了兩個(gè)可迭代對(duì)象 (iterable),不嚴(yán)謹(jǐn)?shù)卣f,容器類型數(shù)據(jù) (str, tuple, list, dict, set) 都是可迭代對(duì)象。
下面寫出列表、字典和集合解析式的偽代碼 (pseudo code)。 # list comprehension [值 for 元素 in 可迭代對(duì)象 if 條件] # dict comprehension {鍵值對(duì) for 元素 in 可迭代對(duì)象 if 條件} # set comprehension {值 for 元素 in 可迭代對(duì)象 if 條件} 不難發(fā)現(xiàn),這些解析式都有
對(duì),解析式就是為了把「帶條件的 for 循環(huán)」簡(jiǎn)化成一行代碼的。 也不難發(fā)現(xiàn),列表解析式整個(gè)語句用「中括號(hào) []」框住,而字典和集合解析式整個(gè)語句中「大括號(hào) {}」框住。想想 list, dict 和 set 用什么括號(hào)定義就明白了。 通過這兩個(gè)發(fā)現(xiàn),我們大概對(duì)解析式有個(gè)一些直觀但還比較模糊的理解,根據(jù) input-operation-output 這個(gè)過程總結(jié):
有點(diǎn)抽象?我知道,下節(jié)用「列表解析式」來進(jìn)一步舉例說明。 5.2 列表解析式 問題:如何從一個(gè)含整數(shù)列表中把奇數(shù) (odd number) 挑出來? 簡(jiǎn)單,用帶 if 的 for 循環(huán)唄。
[1, 3, 5] 任務(wù)完成了,但這個(gè)代碼有好幾行呢,不簡(jiǎn)潔,看看下面這一行代碼:
[1, 3, 5] 咋一看從「for 循環(huán)」到「解析式」不直觀,我來用不同顏色把這個(gè)過程可視化一下,如下圖: 你可以把「for 循環(huán)」到「解析式」的過程想像成一個(gè)「復(fù)制-粘貼」的過程:
現(xiàn)在清楚多了吧,在把上面具體的例子推廣到一般的例子,從「for 循環(huán)」到「列表解析式」的過程如下: 因此現(xiàn)在你可以一口氣寫出「列表解析式」了吧,或者可以一口氣讀懂別人寫的「列表解析式」了吧。下節(jié)我們會(huì)用幾個(gè)實(shí)例來鞏固下理解。 現(xiàn)在你可能會(huì)說上面「for 循環(huán)」只有一層,如果兩層怎么轉(zhuǎn)換「列表解析式」?具體來說怎么解決下面這個(gè)問題。 問題:如何用「列表解析式」將一個(gè)二維列表中的元素按行一個(gè)個(gè)展平? 沒思路?先用「for 循環(huán)」試試?
套用一維「列表解析式」的做法 兩點(diǎn)需要注意:
我承認(rèn)我一開始也習(xí)慣寫成下圖錯(cuò)誤的那種 (多練幾次就可以改過來了), 我們把「列表解析式」那一套舉一反三的用到其他解析式上,用下面兩圖理解一下「字典解析式」和「集合解析式」。 再回顧下三種解析式,我們發(fā)現(xiàn)其實(shí)它們都可以實(shí)現(xiàn)上節(jié)提到的 filter 和 map 函數(shù)的功能,用專業(yè)計(jì)算機(jī)的語言說,解析式可以看成是 filter 和 map 函數(shù)的語法糖。 了解完概念,我們看看為什么說「列表解析式」是 「map/filter」的語法糖,兩者的類比圖如下: 首先發(fā)現(xiàn)兩者都是把原列表根據(jù)某些條件轉(zhuǎn)換成新列表,再者
為了達(dá)到相同目的,明顯「列表解析式」是種更簡(jiǎn)潔的方式。 用「在列表中先找出奇數(shù)再乘以 2」的例子,對(duì)于列表 lst = [1, 2, 3, 4, 5],我們先看「列表解析式」的實(shí)現(xiàn): [ n*2 for n in lst if n%2 == 1]
再看「map/filter」的實(shí)現(xiàn): list( map(lambda n: n*2, filter(lambda n: n%2 == 1, lst)) )
誰簡(jiǎn)誰繁,一目了然。 5.3 小例子 問題:用解析式將二維元組里每個(gè)元素提取出來并存儲(chǔ)到一個(gè)列表中。 tup = ((1, 2, 3), (4, 5, 6), (7, 8, 9)) 先遍歷第一層元組,用 for t in tup,然后遍歷第二層元組,用 for x in t,提取每個(gè) x 并'放在“列表中,用 []。代碼如下:
[1, 2, 3, 4, 5, 6, 7, 8, 9] 至于為什么按 for t in tup for x in t 這個(gè)順序?qū)?,還記得上節(jié)的這張圖嗎? 如果我們想把上面「二維元組」轉(zhuǎn)換成「二維列表」呢?
[[1, 2, 3], [4, 5, 6], [7, 8, 9]] 回到上篇引言的問題。 問題:用解析式把以下這個(gè)不規(guī)則的列表 a 打平 (flatten)?
用解析式一步到位解決上面問題有點(diǎn)難,特別是列表 a 不規(guī)則,每個(gè)元素還可以是 n 層列表,因此我們需要遞推函數(shù) (recursive function),即一個(gè)函數(shù)里面又調(diào)用自己。 def f(x): if type(x) is list: return [y for l in x for y in f(l)] else: return [x]
f(a)
整個(gè)列表遍歷一遍,有四個(gè)元素,1, 2, [3, 4] 和 [[5, 6], [7, 8]]。函數(shù) f(x) 是一個(gè)遞推函數(shù),當(dāng) x 是元素,返回 [x],那么
當(dāng) x 是列表,返回 [y for l in x for y in f(l)],當(dāng) x = [3 ,4] 時(shí)
整個(gè) f([3 ,4]) 的返回值是 [3 ,4]。同理,當(dāng) x = [[5, 6], [7, 8]] 時(shí),f(x) 的返回值是 [5, 6, 7, 8]。 到此,列表中四個(gè)元素 1, 2, [3, 4] 和 [[5, 6], [7, 8]] 的情況都分析完畢了,現(xiàn)在當(dāng) x = [1, 2, [3, 4], [[5, 6], [7, 8]]],f(x) 也運(yùn)行到下面這步 return [y for l in x for y in f(l)] 那么
把這所有的 y 再合成一個(gè)列表不就是 [1, 2, 3, 4, 5, 6, 7, 8] 正規(guī) (遞推) 函數(shù)寫好了,把它寫成匿名函數(shù)也很簡(jiǎn)單了。 a = [1, 2, [3, 4], [[5, 6], [7, 8]]] f = lambda x: [y for l in x for y in f(l)] if type(y) is list else [x] f(a)
配著下圖再理解一遍: 本帖討論了函數(shù)和解析式。優(yōu)雅清晰是 python 的核心價(jià)值觀,高階函數(shù)和解析式都符合這個(gè)價(jià)值觀。 函數(shù)包括正規(guī)函數(shù) (用 def) 和匿名函數(shù) (用 lambda),函數(shù)的參數(shù)形態(tài)也多種多樣,有位置參數(shù)、默認(rèn)參數(shù)、可變參數(shù)、關(guān)鍵字參數(shù)、命名關(guān)鍵字參數(shù)。匿名函數(shù)主要用在高階函數(shù)中,高階函數(shù)的參數(shù)可以是函數(shù) (Python 里面內(nèi)置 map/filter/reduce 函數(shù)),返回值也可以是參數(shù) (閉包、偏函數(shù)、柯里化函數(shù))。 解析式并沒有解決新的問題,只是以一種更加簡(jiǎn)潔,可讀性更高的方式解決老的問題。解析式可以把「帶 if 條件的 for 循環(huán)」用一行程序表達(dá)出來,也可以實(shí)現(xiàn) map 加 filter 的功能。 最后用 Tim Peters 的 The Zen of Python 結(jié)尾。 Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one -- and preferably only one -- obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than right now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those! 下篇討論 Python 中用于數(shù)組計(jì)算和操作的 NumPy。Stay Tuned! |
|