如何使用正確的姿勢進行高效Python函數(shù)式編程您旁?

演講者:丁來強@Splunk ?PyConChina2015 北京站

9月12日與9月19日靴寂,PyConChina 2015上海站與北京站順利落下帷幕磷蜀。“人生苦短百炬,Python 當歌”是本屆的主題褐隆。 在PyCon北京場,Splunk的Tech Lead丁來強為大家?guī)砹藘蓤龈韶洕M滿的技術(shù)分享剖踊,收獲了業(yè)內(nèi)無數(shù)好評庶弃。

關(guān)于函數(shù)式編程

有哪些函數(shù)式語言?

其實函數(shù)是語言很早就出現(xiàn)了德澈,上世紀30年代出現(xiàn)的Lambda和50年代的LISP歇攻,比面向過程和對象的語言出現(xiàn)的更早,現(xiàn)代的Clojure梆造,Erlang缴守,Haskeel也為很多人所熟知,保持著很強的活力镇辉,Scala作為Spark和Kafka的實現(xiàn)語言也是相當?shù)幕稹?/p>

什么是函數(shù)式語言屡穗?

和面向過程的編程語言(例如C等)和面向?qū)ο蟮恼Z言(例如C++/Java等)相比,函數(shù)式語言是一種聲明式的編程規(guī)約范式忽肛。 簡單例子如下:

一個復雜些的例子:

計算如下字符串的值:expr = "28+32+++32++39” ==> 28+32+32+39 ==> 131

命令式的語言采用一個初始值村砂,然后一直去是修改它,最終獲得結(jié)果屹逛。

而函數(shù)式風格通過函數(shù)的組合調(diào)用箍镜,通過函數(shù)的一層層轉(zhuǎn)換輸入輸出最終獲得結(jié)果。

作為一種風格煎源,很多人的代碼里面可能已經(jīng)有一些是函數(shù)式的了色迂。

函數(shù)式編程的特點

函數(shù)式編程有如下特點:函數(shù)即為數(shù)據(jù),第一等公民

高階函數(shù)

純函數(shù): 避免狀態(tài)手销,無副作用

不可變數(shù)據(jù)結(jié)構(gòu)

強編譯器

尾遞歸消除(TRE)

延遲歇僧,模式匹配(Pattern Match),Monads

這個議題除了Monads锋拖,其他都有所覆蓋诈悍。

回到Python,Python其實是一個具備了很強函數(shù)式能力的命令式編程語言兽埃,通過語言或者庫的支持侥钳,對以上幾乎所有特征都有所支持(除了強編譯器)。

一些函數(shù)語言編譯執(zhí)行器可以在強預設(shè)下做很強的優(yōu)化柄错,例如直接并發(fā)舷夺,延遲處理或者次序調(diào)換等苦酱。 而Python卻沒有這一點支持,歸根結(jié)底是因為Python從一開始就是按照命令式語言進行設(shè)計的给猾。 Guido大叔曾經(jīng)說過這樣一段話:

"I have never considered Python to be heavily influenced by functional languages, ... I had made functions first-class objects, I didn't view Python as a functional programming language..."

盡管如此疫萤,函數(shù)式編程風格依然是一種非常不錯的風格。 主要有幾個原因:

更好的測試性(因為無狀態(tài))敢伸,也更可靠

更擅長流式與并發(fā)操作(例如Scala)

一些偏主觀的觀點: 例如函數(shù)式編程風格有的時候提供了一種更加簡潔巧妙的解決方案扯饶。 代碼更少,可讀性更好池颈。

純函數(shù)第一等公民就像Guido所說尾序,Python中的函數(shù)已經(jīng)是第一等公民了。皆可以作為變量躯砰,也可以作為參數(shù)傳入傳出蹲诀,也可以隨時Lambda定義,或者放入數(shù)據(jù)弃揽,所有操作符也都是已經(jīng)函數(shù)化的了。

通過fn庫则北,函數(shù)定義的方式可以進一步簡化為Scala風格:

純函數(shù)

無副作用

無副作用體現(xiàn)在對輸入的數(shù)據(jù)本身無修改矿微,對函數(shù)內(nèi)部外部無狀態(tài)修改。

如下的例子都是一些反例尚揣。

修改了輸入

修改了外界狀態(tài)

修改了輸出涌矢,影響了原輸入

真正純的無狀態(tài)和副作用的函數(shù)應該如下:

但是這可能比較復雜,性能也不太好快骗。 這就要引入函數(shù)編程里的可持久化數(shù)據(jù)結(jié)構(gòu)娜庇。

可持久化數(shù)據(jù)結(jié)構(gòu)一種支持修改,在不修改原版本的情況下方篮,返回一個修改版本的數(shù)據(jù)結(jié)構(gòu)名秀。

Persistent Data

高階函數(shù)

高階函數(shù)就是接受或者返回函數(shù)的函數(shù)。

Python已有不錯的支持:map藕溅,filter匕得,groupby,reduce

functools module

list comprehension

decorators

Mapmap是函數(shù)式編程語言中很重要的高階函數(shù)巾表,接受函數(shù)對輸入進行轉(zhuǎn)換汁掠。

Filter

reduce接受函數(shù)對輸入進行過濾。

List Comprehension

Map/Filter在函數(shù)式編程中非常重要集币,然后Python里面list Comprehension可能適用的更加廣泛考阱,過濾轉(zhuǎn)換,最終構(gòu)造出list鞠苟,set乞榨,dict等都非常簡單秽之。

然而List Comprehension一些特性也需要注意,首先是第一層才是不可修改的姜凄,對于初學者而言政溃,讀取方式也稍微奇怪(先for,再if态秧,最后看開頭)董虱,另外內(nèi)部存在for/if,并沒有函數(shù)模塊化申鱼。

GroupbyGroupby接受函數(shù)對數(shù)據(jù)進行分組:

ReduceReduce接受二元函數(shù)對數(shù)據(jù)進行聚集:

Reduce的實現(xiàn)可以理解為如下:

相對應的sum愤诱,mul也可以直接使用reduce來完成

Partial

首先一個簡單問題,如何構(gòu)造一個默認是降序排列的Sorted2函數(shù)捐友,如下:

一般的實現(xiàn):

而使用Partial則簡單的多淫半。

Partial還可以用來預先參數(shù)綁定。 例如:

ComposeCompose是常用來構(gòu)建更高級函數(shù)的工具:

CurryingCurrying是對Partial的更進一步的擴展:

toolz.curried里面所有的函數(shù)都已經(jīng)Curry化了匣砖。

Currying對于簡化參數(shù)化Decorator也是非常有用的科吭。 例如:

遞歸相關(guān)技術(shù)

關(guān)于遞歸

一些函數(shù)式語言里面沒有l(wèi)oop,只能用遞歸猴鲫。 而通常都支持尾遞歸消除(將遞歸轉(zhuǎn)化為內(nèi)部loop)

用遞歸的理由

代碼邏輯更清晰对人。例如:

不用遞歸的原因三個原因使得遞歸沒有大量被使用,因為:遞歸調(diào)用有遞歸層數(shù)限制(Python是1000)拂共,超過會棧溢出牺弄。

重復計算。 fib(n-2)與fib(n-1)是存在重復計算的宜狐。

遞歸調(diào)用常常需要不同情況進行跳轉(zhuǎn)势告,需要大量使用overloading或者pattern match的技術(shù)。

關(guān)于尾遞歸消除(優(yōu)化)尾遞歸優(yōu)化可以消除遞歸層數(shù)的限制抚恒,要求遞歸只存在于函數(shù)調(diào)用的最后一行咱台,并且沒有進一步計算。

如下是反例:

通常使用一個幫助函數(shù)俭驮,將計算放在計算放在參數(shù)傳遞時吵护,是常用技巧:

Trampoline然而壞消息是: Python并不支持尾遞歸消除!(Guido: 怪我咯表鳍!)

但并不用擔心馅而,Tranpline就是用來解決這個問題的。 添加fn.recur的decorator譬圣,對于要結(jié)束遞歸的分支瓮恭,返回False開頭的tuple,否則返回True開頭的tuple即可厘熟。

消除重復計算Python自帶的lru_cache即可消除重復計算的問題:

另外推薦(cy)toolz里面的memoize屯蹦,支持更多功能维哈,例如cache可以讓代碼更簡潔。

支持重載Python語言本身是不支持函數(shù)重載的登澜,但其語言自身函數(shù)功能也很強大:未命名參數(shù)阔挠,命名參數(shù),變參脑蠕,命名變參购撼,解包機制等。

讓Python支持類似于C++/Java等里面的重載谴仙,只需要引入multipledispatch.dsipatch即可迂求,需要注意一開始的初始化。

重載使得遞歸的邏輯更加簡潔

Haskell類強大的pattern match功能不僅支持類型重載晃跺,也支持參數(shù)特征匹配揩局。 這在Python中通過庫也是支持的。 至于實現(xiàn)機制掀虎,有興趣的朋友可以看一下Python AST凌盯。

延遲遍歷器帶來的延遲計算是Python核心慣用法。 常見的例子有:xrange

tuple comprehension

itertools 模塊

dict.iter* 方法

generator

for-loop 協(xié)議

fn.Stream提供了進一步的語法糖烹玉,例如給跌代添加切片功能驰怎。

Generator對于實現(xiàn)無限迭代器是很方便的。

fn.Stream也支持通過流方式來實現(xiàn)春霍。

更多迭代器可以在(cy)toolz.itertoolz中可以找到:統(tǒng)計: count,groupby叶眉,frequency

過濾: unique址儒,partition

選擇: take,drop衅疙,first莲趣,last,n_th etc饱溢。

merge_sorted

并行

值得一提的是函數(shù)式編程天生就是支持并行的喧伞。

Map因為傳遞的函數(shù)是無狀態(tài)無副作用的,所以可以直接并發(fā)執(zhí)行绩郎,加快執(zhí)行效率潘鲫。

Reduce同理,Reduce也是可以并發(fā)執(zhí)行來進行二元聚集最終實現(xiàn)Log級別的性能優(yōu)化肋杖。

Python多進程與分布式策略算法大師Knuth說過:"97%過早優(yōu)化是罪惡之源"溉仑,在選擇多進程或者分布式的時候考慮是否是唯一選擇。 可能的其他選項有:

選擇不同的Python解釋器: Cython状植,PyPy浊竟,numba等

某些情況下分布式增加的管理復雜度不如單點增加多核來的有效怨喘。

通過GPU提高計算效率是數(shù)據(jù)科學領(lǐng)域的一個趨勢。

IO密集型并一定普遍適用于增加多進程的情況振定。

Python并發(fā)選擇GIL的原因必怜,計算密集型是的多線程沒有意義。

Python自帶multiprocessing庫提供了很不錯的高階接口后频。

分布式通用領(lǐng)域計算模型的選擇有Spark梳庆,Hadoop,Celery等對于數(shù)據(jù)科學方面徘郭,分布式numpy和全局數(shù)據(jù)矩陣發(fā)展的也非晨恳妫快。

Python并發(fā)分布式庫可較為成熟残揉,供選擇的也很多:自帶的Multiprocessing/RPC庫

IPython Cluster

scikit-learn 并行算法

Python Parallel(只有Py2)胧后,Celery

更多: joblib等

并發(fā)計算與數(shù)據(jù)分發(fā)并行計算只需要替換現(xiàn)有默認函數(shù)為并發(fā)函數(shù)即可。 例如Pool.map取代模塊的map抱环。

然而并發(fā)與分布式計算需要考慮如何把數(shù)據(jù)傳入傳出模塊壳快,一般的數(shù)據(jù)都是可以的。 然而Closure默認不能pickle化镇草,這種情況下需要使用copy_reg擴展或者使用dill庫眶痰。

IPython Cluster因為使用dill庫,并不存在這個問題梯啤。

如下圖是自帶多進程庫竖伯,IPython Cluster與Celery的一個比較,其中橙色勾表示需要一些額外代碼來支持因宇,叉表示需要較多額外工作支持七婴。

總結(jié)

通過來強深入淺出的介紹,大家了解了如何使用Python進行高逼格函數(shù)式編程的技術(shù)察滑,工具和實踐打厘。 使用Python也可以享受函數(shù)編程所帶來的高模塊,可復用贺辰,并發(fā)流處理等方面的好處户盯。

歡迎報名2016年P(guān)yConChina大會(北京、上海饲化、深圳)莽鸭,本次大會丁來強將在三個會場進行分享,點擊報名大會吃靠。或掃描下面圖片的二維碼進入報名頁蒋川。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市撩笆,隨后出現(xiàn)的幾起案子捺球,更是在濱河造成了極大的恐慌缸浦,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氮兵,死亡現(xiàn)場離奇詭異裂逐,居然都是意外死亡,警方通過查閱死者的電腦和手機泣栈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門卜高,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人南片,你說我怎么就攤上這事掺涛。” “怎么了疼进?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵薪缆,是天一觀的道長。 經(jīng)常有香客問我伞广,道長拣帽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任嚼锄,我火速辦了婚禮减拭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘区丑。我一直安慰自己拧粪,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布沧侥。 她就那樣靜靜地躺著可霎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪正什。 梳的紋絲不亂的頭發(fā)上啥纸,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天号杏,我揣著相機與錄音婴氮,去河邊找鬼。 笑死盾致,一個胖子當著我的面吹牛主经,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庭惜,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼罩驻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了护赊?” 一聲冷哼從身側(cè)響起惠遏,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤砾跃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后节吮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抽高,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年透绩,在試婚紗的時候發(fā)現(xiàn)自己被綠了翘骂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡帚豪,死狀恐怖碳竟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狸臣,我是刑警寧澤莹桅,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站固棚,受9級特大地震影響统翩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜此洲,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一厂汗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呜师,春花似錦娶桦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至知牌,卻和暖如春祈争,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背角寸。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工菩混, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扁藕。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓沮峡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亿柑。 傳聞我的和親對象是個殘疾皇子邢疙,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容