python partition函数_如何使用正确的姿势进行高效Python函数式编程?
演講者:丁來(lái)強(qiáng)@Splunk ?PyConChina2015 北京站
9月12日與9月19日,PyConChina 2015上海站與北京站順利落下帷幕。“人生苦短,Python 當(dāng)歌”是本屆的主題。 在PyCon北京場(chǎng),Splunk的Tech Lead丁來(lái)強(qiáng)為大家?guī)?lái)了兩場(chǎng)干貨滿滿的技術(shù)分享,收獲了業(yè)內(nèi)無(wú)數(shù)好評(píng)。
關(guān)于函數(shù)式編程
有哪些函數(shù)式語(yǔ)言?
其實(shí)函數(shù)是語(yǔ)言很早就出現(xiàn)了,上世紀(jì)30年代出現(xiàn)的Lambda和50年代的LISP,比面向過(guò)程和對(duì)象的語(yǔ)言出現(xiàn)的更早,現(xiàn)代的Clojure,Erlang,Haskeel也為很多人所熟知,保持著很強(qiáng)的活力,Scala作為Spark和Kafka的實(shí)現(xiàn)語(yǔ)言也是相當(dāng)?shù)幕稹?/p>
什么是函數(shù)式語(yǔ)言?
和面向過(guò)程的編程語(yǔ)言(例如C等)和面向?qū)ο蟮恼Z(yǔ)言(例如C++/Java等)相比,函數(shù)式語(yǔ)言是一種聲明式的編程規(guī)約范式。 簡(jiǎn)單例子如下:
一個(gè)復(fù)雜些的例子:
計(jì)算如下字符串的值:expr = "28+32+++32++39” ==> 28+32+32+39 ==> 131
命令式的語(yǔ)言采用一個(gè)初始值,然后一直去是修改它,最終獲得結(jié)果。
而函數(shù)式風(fēng)格通過(guò)函數(shù)的組合調(diào)用,通過(guò)函數(shù)的一層層轉(zhuǎn)換輸入輸出最終獲得結(jié)果。
作為一種風(fēng)格,很多人的代碼里面可能已經(jīng)有一些是函數(shù)式的了。
函數(shù)式編程的特點(diǎn)
函數(shù)式編程有如下特點(diǎn):函數(shù)即為數(shù)據(jù),第一等公民
高階函數(shù)
純函數(shù): 避免狀態(tài),無(wú)副作用
不可變數(shù)據(jù)結(jié)構(gòu)
強(qiáng)編譯器
尾遞歸消除(TRE)
延遲,模式匹配(Pattern Match),Monads
這個(gè)議題除了Monads,其他都有所覆蓋。
回到Python,Python其實(shí)是一個(gè)具備了很強(qiáng)函數(shù)式能力的命令式編程語(yǔ)言,通過(guò)語(yǔ)言或者庫(kù)的支持,對(duì)以上幾乎所有特征都有所支持(除了強(qiáng)編譯器)。
一些函數(shù)語(yǔ)言編譯執(zhí)行器可以在強(qiáng)預(yù)設(shè)下做很強(qiáng)的優(yōu)化,例如直接并發(fā),延遲處理或者次序調(diào)換等。 而Python卻沒(méi)有這一點(diǎn)支持,歸根結(jié)底是因?yàn)镻ython從一開始就是按照命令式語(yǔ)言進(jìn)行設(shè)計(jì)的。 Guido大叔曾經(jīng)說(shuō)過(guò)這樣一段話:
"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ù)式編程風(fēng)格依然是一種非常不錯(cuò)的風(fēng)格。 主要有幾個(gè)原因:
更好的測(cè)試性(因?yàn)闊o(wú)狀態(tài)),也更可靠
更擅長(zhǎng)流式與并發(fā)操作(例如Scala)
一些偏主觀的觀點(diǎn): 例如函數(shù)式編程風(fēng)格有的時(shí)候提供了一種更加簡(jiǎn)潔巧妙的解決方案。 代碼更少,可讀性更好。
純函數(shù)第一等公民就像Guido所說(shuō),Python中的函數(shù)已經(jīng)是第一等公民了。皆可以作為變量,也可以作為參數(shù)傳入傳出,也可以隨時(shí)Lambda定義,或者放入數(shù)據(jù),所有操作符也都是已經(jīng)函數(shù)化的了。
通過(guò)fn庫(kù),函數(shù)定義的方式可以進(jìn)一步簡(jiǎn)化為Scala風(fēng)格:
純函數(shù)
無(wú)副作用
無(wú)副作用體現(xiàn)在對(duì)輸入的數(shù)據(jù)本身無(wú)修改,對(duì)函數(shù)內(nèi)部外部無(wú)狀態(tài)修改。
如下的例子都是一些反例。
修改了輸入
修改了外界狀態(tài)
修改了輸出,影響了原輸入
真正純的無(wú)狀態(tài)和副作用的函數(shù)應(yīng)該如下:
但是這可能比較復(fù)雜,性能也不太好。 這就要引入函數(shù)編程里的可持久化數(shù)據(jù)結(jié)構(gòu)。
可持久化數(shù)據(jù)結(jié)構(gòu)一種支持修改,在不修改原版本的情況下,返回一個(gè)修改版本的數(shù)據(jù)結(jié)構(gòu)。
Persistent Data
高階函數(shù)
高階函數(shù)就是接受或者返回函數(shù)的函數(shù)。
Python已有不錯(cuò)的支持:map,filter,groupby,reduce
functools module
list comprehension
decorators
Mapmap是函數(shù)式編程語(yǔ)言中很重要的高階函數(shù),接受函數(shù)對(duì)輸入進(jìn)行轉(zhuǎn)換。
Filter
reduce接受函數(shù)對(duì)輸入進(jìn)行過(guò)濾。
List Comprehension
Map/Filter在函數(shù)式編程中非常重要,然后Python里面list Comprehension可能適用的更加廣泛,過(guò)濾轉(zhuǎn)換,最終構(gòu)造出list,set,dict等都非常簡(jiǎn)單。
然而List Comprehension一些特性也需要注意,首先是第一層才是不可修改的,對(duì)于初學(xué)者而言,讀取方式也稍微奇怪(先f(wàn)or,再if,最后看開頭),另外內(nèi)部存在for/if,并沒(méi)有函數(shù)模塊化。
GroupbyGroupby接受函數(shù)對(duì)數(shù)據(jù)進(jìn)行分組:
ReduceReduce接受二元函數(shù)對(duì)數(shù)據(jù)進(jìn)行聚集:
Reduce的實(shí)現(xiàn)可以理解為如下:
相對(duì)應(yīng)的sum,mul也可以直接使用reduce來(lái)完成
Partial
首先一個(gè)簡(jiǎn)單問(wèn)題,如何構(gòu)造一個(gè)默認(rèn)是降序排列的Sorted2函數(shù),如下:
一般的實(shí)現(xiàn):
而使用Partial則簡(jiǎn)單的多。
Partial還可以用來(lái)預(yù)先參數(shù)綁定。 例如:
ComposeCompose是常用來(lái)構(gòu)建更高級(jí)函數(shù)的工具:
CurryingCurrying是對(duì)Partial的更進(jìn)一步的擴(kuò)展:
toolz.curried里面所有的函數(shù)都已經(jīng)Curry化了。
Currying對(duì)于簡(jiǎn)化參數(shù)化Decorator也是非常有用的。 例如:
遞歸相關(guān)技術(shù)
關(guān)于遞歸
一些函數(shù)式語(yǔ)言里面沒(méi)有l(wèi)oop,只能用遞歸。 而通常都支持尾遞歸消除(將遞歸轉(zhuǎn)化為內(nèi)部loop)
用遞歸的理由
代碼邏輯更清晰。例如:
不用遞歸的原因三個(gè)原因使得遞歸沒(méi)有大量被使用,因?yàn)?#xff1a;遞歸調(diào)用有遞歸層數(shù)限制(Python是1000),超過(guò)會(huì)棧溢出。
重復(fù)計(jì)算。 fib(n-2)與fib(n-1)是存在重復(fù)計(jì)算的。
遞歸調(diào)用常常需要不同情況進(jìn)行跳轉(zhuǎn),需要大量使用overloading或者pattern match的技術(shù)。
關(guān)于尾遞歸消除(優(yōu)化)尾遞歸優(yōu)化可以消除遞歸層數(shù)的限制,要求遞歸只存在于函數(shù)調(diào)用的最后一行,并且沒(méi)有進(jìn)一步計(jì)算。
如下是反例:
通常使用一個(gè)幫助函數(shù),將計(jì)算放在計(jì)算放在參數(shù)傳遞時(shí),是常用技巧:
Trampoline然而壞消息是: Python并不支持尾遞歸消除!(Guido: 怪我咯!)
但并不用擔(dān)心,Tranpline就是用來(lái)解決這個(gè)問(wèn)題的。 添加fn.recur的decorator,對(duì)于要結(jié)束遞歸的分支,返回False開頭的tuple,否則返回True開頭的tuple即可。
消除重復(fù)計(jì)算Python自帶的lru_cache即可消除重復(fù)計(jì)算的問(wèn)題:
另外推薦(cy)toolz里面的memoize,支持更多功能,例如cache可以讓代碼更簡(jiǎn)潔。
支持重載Python語(yǔ)言本身是不支持函數(shù)重載的,但其語(yǔ)言自身函數(shù)功能也很強(qiáng)大:未命名參數(shù),命名參數(shù),變參,命名變參,解包機(jī)制等。
讓Python支持類似于C++/Java等里面的重載,只需要引入multipledispatch.dsipatch即可,需要注意一開始的初始化。
重載使得遞歸的邏輯更加簡(jiǎn)潔
Haskell類強(qiáng)大的pattern match功能不僅支持類型重載,也支持參數(shù)特征匹配。 這在Python中通過(guò)庫(kù)也是支持的。 至于實(shí)現(xiàn)機(jī)制,有興趣的朋友可以看一下Python AST。
延遲遍歷器帶來(lái)的延遲計(jì)算是Python核心慣用法。 常見的例子有:xrange
tuple comprehension
itertools 模塊
dict.iter* 方法
generator
for-loop 協(xié)議
fn.Stream提供了進(jìn)一步的語(yǔ)法糖,例如給跌代添加切片功能。
Generator對(duì)于實(shí)現(xiàn)無(wú)限迭代器是很方便的。
fn.Stream也支持通過(guò)流方式來(lái)實(shí)現(xiàn)。
更多迭代器可以在(cy)toolz.itertoolz中可以找到:統(tǒng)計(jì): count,groupby,frequency
過(guò)濾: unique,partition
選擇: take,drop,first,last,n_th etc。
merge_sorted
并行
值得一提的是函數(shù)式編程天生就是支持并行的。
Map因?yàn)閭鬟f的函數(shù)是無(wú)狀態(tài)無(wú)副作用的,所以可以直接并發(fā)執(zhí)行,加快執(zhí)行效率。
Reduce同理,Reduce也是可以并發(fā)執(zhí)行來(lái)進(jìn)行二元聚集最終實(shí)現(xiàn)Log級(jí)別的性能優(yōu)化。
Python多進(jìn)程與分布式策略算法大師Knuth說(shuō)過(guò):"97%過(guò)早優(yōu)化是罪惡之源",在選擇多進(jìn)程或者分布式的時(shí)候考慮是否是唯一選擇。 可能的其他選項(xiàng)有:
選擇不同的Python解釋器: Cython,PyPy,numba等
某些情況下分布式增加的管理復(fù)雜度不如單點(diǎn)增加多核來(lái)的有效。
通過(guò)GPU提高計(jì)算效率是數(shù)據(jù)科學(xué)領(lǐng)域的一個(gè)趨勢(shì)。
IO密集型并一定普遍適用于增加多進(jìn)程的情況。
Python并發(fā)選擇GIL的原因,計(jì)算密集型是的多線程沒(méi)有意義。
Python自帶multiprocessing庫(kù)提供了很不錯(cuò)的高階接口。
分布式通用領(lǐng)域計(jì)算模型的選擇有Spark,Hadoop,Celery等對(duì)于數(shù)據(jù)科學(xué)方面,分布式numpy和全局?jǐn)?shù)據(jù)矩陣發(fā)展的也非常快。
Python并發(fā)分布式庫(kù)可較為成熟,供選擇的也很多:自帶的Multiprocessing/RPC庫(kù)
IPython Cluster
scikit-learn 并行算法
Python Parallel(只有Py2),Celery
更多: joblib等
并發(fā)計(jì)算與數(shù)據(jù)分發(fā)并行計(jì)算只需要替換現(xiàn)有默認(rèn)函數(shù)為并發(fā)函數(shù)即可。 例如Pool.map取代模塊的map。
然而并發(fā)與分布式計(jì)算需要考慮如何把數(shù)據(jù)傳入傳出模塊,一般的數(shù)據(jù)都是可以的。 然而Closure默認(rèn)不能pickle化,這種情況下需要使用copy_reg擴(kuò)展或者使用dill庫(kù)。
IPython Cluster因?yàn)槭褂胐ill庫(kù),并不存在這個(gè)問(wèn)題。
如下圖是自帶多進(jìn)程庫(kù),IPython Cluster與Celery的一個(gè)比較,其中橙色勾表示需要一些額外代碼來(lái)支持,叉表示需要較多額外工作支持。
總結(jié)
通過(guò)來(lái)強(qiáng)深入淺出的介紹,大家了解了如何使用Python進(jìn)行高逼格函數(shù)式編程的技術(shù),工具和實(shí)踐。 使用Python也可以享受函數(shù)編程所帶來(lái)的高模塊,可復(fù)用,并發(fā)流處理等方面的好處。
總結(jié)
以上是生活随笔為你收集整理的python partition函数_如何使用正确的姿势进行高效Python函数式编程?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: visual studio code p
- 下一篇: ipv6计算_移动云多款产品通过工信部I