工作的項(xiàng)目主要是以Python進(jìn)行開(kāi)發(fā)的斥季,經(jīng)常會(huì)看到項(xiàng)目代碼中的lambda表達(dá)式還有一些把函數(shù)作為參數(shù)或放在集合中的用法。之前只是淺顯的知道是函數(shù)式編程纤壁,這里好好研究一下受楼。下面從以下幾個(gè)部分進(jìn)行學(xué)習(xí):
- 什么是函數(shù)式編程?
- 函數(shù)式編程的特點(diǎn)?
- 函數(shù)式編程的用途?
- Python中的函數(shù)式編程
什么是函數(shù)式編程?
簡(jiǎn)單說(shuō)银萍,"函數(shù)式編程"是一種"編程范式"(programming paradigm),也就是如何編寫(xiě)程序的方法論恤左。
它屬于"結(jié)構(gòu)化編程"的一種贴唇,主要思想是把運(yùn)算過(guò)程盡量寫(xiě)成一系列嵌套的函數(shù)調(diào)用。
函數(shù)式編程關(guān)心類(lèi)型(代數(shù)結(jié)構(gòu))之間的關(guān)系飞袋,是一種抽象程度很高的編程范式戳气;
而命令式編程則關(guān)心解決問(wèn)題的步驟。
函數(shù)式編程特點(diǎn)
函數(shù)式編程具有五個(gè)鮮明的特點(diǎn)巧鸭。
- 函數(shù)是"第一等公民"
所謂"第一等公民"(first class)瓶您,指的是函數(shù)與其他數(shù)據(jù)類(lèi)型一樣,處于平等地位纲仍,可以賦值給其他變量呀袱,也可以作為參數(shù),傳入另一個(gè)函數(shù)郑叠,或者作為別的函數(shù)的返回值夜赵。
舉例來(lái)說(shuō),下面代碼中的print變量就是一個(gè)函數(shù)乡革,可以作為另一個(gè)函數(shù)的參數(shù)寇僧。
var print = function(i){ console.log(i);};
[1,2,3].forEach(print);
- 只用"表達(dá)式"摊腋,不用"語(yǔ)句"
"表達(dá)式"(expression)是一個(gè)單純的運(yùn)算過(guò)程,總是有返回值嘁傀;"語(yǔ)句"(statement)是執(zhí)行某種操作兴蒸,沒(méi)有返回值。函數(shù)式編程要求细办,只使用表達(dá)式橙凳,不使用語(yǔ)句。也就是說(shuō)蟹腾,每一步都是單純的運(yùn)算痕惋,而且都有返回值区宇。
原因是函數(shù)式編程的開(kāi)發(fā)動(dòng)機(jī)娃殖,一開(kāi)始就是為了處理運(yùn)算(computation),不考慮系統(tǒng)的讀寫(xiě)(I/O)议谷。"語(yǔ)句"屬于對(duì)系統(tǒng)的讀寫(xiě)操作炉爆,所以就被排斥在外。
3. 沒(méi)有"副作用"
所謂"副作用"(side effect)卧晓,指的是函數(shù)內(nèi)部與外部互動(dòng)(最典型的情況芬首,就是修改全局變量的值),產(chǎn)生運(yùn)算以外的其他結(jié)果逼裆。
函數(shù)式編程強(qiáng)調(diào)沒(méi)有"副作用"郁稍,意味著函數(shù)要保持獨(dú)立,所有功能就是返回一個(gè)新的值胜宇,沒(méi)有其他行為耀怜,尤其是不得修改外部變量的值。
- 不修改狀態(tài)
上一點(diǎn)已經(jīng)提到桐愉,函數(shù)式編程只是返回新的值财破,不修改系統(tǒng)變量。因此从诲,不修改變量左痢,也是它的一個(gè)重要特點(diǎn)。
5. 引用透明
引用透明(Referential transparency)系洛,指的是函數(shù)的運(yùn)行不依賴(lài)于外部變量或"狀態(tài)"俊性,只依賴(lài)于輸入的參數(shù),任何時(shí)候只要參數(shù)相同描扯,引用函數(shù)所得到的返回值總是相同的磅废。
函數(shù)式編程的作用
- 代碼簡(jiǎn)潔,開(kāi)發(fā)快速
- 接近自然語(yǔ)言荆烈,易于理解
- 更方便的代碼管理
- 易于"并發(fā)編程"
Python中的函數(shù)式編程
在Python中拯勉,函數(shù)式編程主要通過(guò)使用:
- 高階函數(shù)
- 返回函數(shù)
- lambda函數(shù)
- 裝飾器
- 偏函數(shù)
高階函數(shù)
一個(gè)函數(shù)就可以接收另一個(gè)函數(shù)作為參數(shù)竟趾,這種函數(shù)就稱(chēng)之為高階函數(shù)。
例如宫峦,
def add(x, y, f):
return f(x) + f(y)
print(add(-5, 6, abs))
Python中還內(nèi)置了三種常用的高階函數(shù):
- map/reduce
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
- filter
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 結(jié)果: [1, 5, 9, 15]
- sorted
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
返回函數(shù)
返回求和函數(shù)
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
閉包:一個(gè)函數(shù)定義中引用了函數(shù)外定義的變量岔帽,并且該函數(shù)可以在其定義環(huán)境外被執(zhí)行
def outer_func():
loc_list = []
def inner_func(name):
loc_list.append(len(loc_list) + 1)
print '%s loc_list = %s' %(name, loc_list)
return inner_func
在這個(gè)例子中我們至少可以對(duì)閉包中引用的自由變量有如下的認(rèn)識(shí):
- 閉包中的引用的自由變量只和具體的閉包有關(guān)聯(lián),閉包的每個(gè)實(shí)例引用的自由變量互不干擾导绷。
- 一個(gè)閉包實(shí)例對(duì)其自由變量的修改會(huì)被傳遞到下一次該閉包實(shí)例的調(diào)用犀勒。
lambda函數(shù)
- 一個(gè)語(yǔ)法
在Python中,lambda的語(yǔ)法是唯一的妥曲。其形式如下:
lambda argument_list: expression
其中贾费,lambda是Python預(yù)留的關(guān)鍵字,argument_list和expression由用戶(hù)自定義檐盟。這里的argument_list是參數(shù)列表褂萧,expression是一個(gè)關(guān)于參數(shù)的表達(dá)式。
這里的lambda argument_list: expression表示的是一個(gè)函數(shù)葵萎。這個(gè)函數(shù)叫做lambda函數(shù)导犹。
- 三個(gè)特性
lambda函數(shù)有如下特性:
- lambda函數(shù)是匿名的:所謂匿名函數(shù),通俗地說(shuō)就是沒(méi)有名字的函數(shù)羡忘。lambda函數(shù)沒(méi)有名字谎痢。
- lambda函數(shù)有輸入和輸出:輸入是傳入到參數(shù)列表argument_list的值,輸出是根據(jù)表達(dá)式expression計(jì)算得到的值卷雕。
- lambda函數(shù)一般功能簡(jiǎn)單:?jiǎn)涡衑xpression決定了lambda函數(shù)不可能完成復(fù)雜的邏輯节猿,只能完成非常簡(jiǎn)單的功能。由于其實(shí)現(xiàn)的功能一目了然漫雕,甚至不需要專(zhuān)門(mén)的名字來(lái)說(shuō)明滨嘱。
下面是一些lambda函數(shù)示例:
- lambda x, y: xy;函數(shù)輸入是x和y蝎亚,輸出是它們的積xy
- lambda:None九孩;函數(shù)沒(méi)有輸入?yún)?shù),輸出是None
- lambda *args: sum(args); 輸入是任意個(gè)數(shù)的參數(shù)发框,輸出是它們的和(隱性要求是輸入?yún)?shù)必須能夠進(jìn)行加法運(yùn)算)
- lambda **kwargs: 1躺彬;輸入是任意鍵值對(duì)參數(shù),輸出是1
- 四個(gè)用法
由于lambda語(yǔ)法是固定的梅惯,其本質(zhì)上只有一種用法宪拥,那就是定義一個(gè)lambda函數(shù)。在實(shí)際中铣减,根據(jù)這個(gè)lambda函數(shù)應(yīng)用場(chǎng)景的不同她君,可以將lambda函數(shù)的用法擴(kuò)展為以下幾種:
-
將lambda函數(shù)賦值給一個(gè)變量,通過(guò)這個(gè)變量間接調(diào)用該lambda函數(shù)葫哗。
例如缔刹,執(zhí)行語(yǔ)句add=lambda x, y: x+y球涛,定義了加法函數(shù)lambda x, y: x+y,并將其賦值給變量add校镐,這樣變量add便成為具有加法功能的函數(shù)亿扁。例如,執(zhí)行add(1,2)鸟廓,輸出為3从祝。 -
將lambda函數(shù)賦值給其他函數(shù),從而將其他函數(shù)用該lambda函數(shù)替換引谜。
例如牍陌,為了把標(biāo)準(zhǔn)庫(kù)time中的函數(shù)sleep的功能屏蔽(Mock),我們可以在程序初始化時(shí)調(diào)用:time.sleep=lambda x:None员咽。這樣毒涧,在后續(xù)代碼中調(diào)用time庫(kù)的sleep函數(shù)將不會(huì)執(zhí)行原有的功能。例如骏融,執(zhí)行time.sleep(3)時(shí)链嘀,程序不會(huì)休眠3秒鐘萌狂,而是什么都不做档玻。 -
將lambda函數(shù)作為其他函數(shù)的返回值,返回給調(diào)用者茫藏。
函數(shù)的返回值也可以是函數(shù)误趴。例如return lambda x, y: x+y返回一個(gè)加法函數(shù)。這時(shí)务傲,lambda函數(shù)實(shí)際上是定義在某個(gè)函數(shù)內(nèi)部的函數(shù)凉当,稱(chēng)之為嵌套函數(shù),或者內(nèi)部函數(shù)售葡。對(duì)應(yīng)的看杭,將包含嵌套函數(shù)的函數(shù)稱(chēng)之為外部函數(shù)。內(nèi)部函數(shù)能夠訪問(wèn)外部函數(shù)的局部變量挟伙,這個(gè)特性是閉包(Closure)編程的基礎(chǔ)楼雹,在這里我們不展開(kāi)。 - 將lambda函數(shù)作為參數(shù)傳遞給其他函數(shù)尖阔。
例如:
dim_met_list = sorted(dim_met_list, key=lambda d: (int(d["map_type"]),
int(d["dim_met_order"])))
list(map(lambda x: DATA_SET_PREFIX + x, DATA_SET_FIELD_SET))
- 一個(gè)爭(zhēng)議
- 支持方認(rèn)為使用lambda編寫(xiě)的代碼更緊湊贮缅,更“pythonic”。
- 反對(duì)方認(rèn)為介却,lambda函數(shù)能夠支持的功能十分有限谴供,其不支持多分支程序if...elif...else...和異常處理程序try ...except...。并且齿坷,lambda函數(shù)的功能被隱藏桂肌,對(duì)于編寫(xiě)代碼之外的人員來(lái)說(shuō)数焊,理解lambda代碼需要耗費(fèi)一定的理解成本。
裝飾器
一種常用的返回函數(shù)崎场,特殊的閉包
偏函數(shù)
>>> int('12345')
12345
>>> int('12345', base=8)
5349
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
總結(jié)
個(gè)人理解在一些數(shù)值計(jì)算和裝飾器場(chǎng)景可以使用函數(shù)式編程使代碼更簡(jiǎn)單昌跌,但是復(fù)雜的業(yè)務(wù)場(chǎng)景不建議使用函數(shù)式編程,會(huì)使代碼難以理解照雁,增加維護(hù)成本蚕愤,降低開(kāi)發(fā)效率。
參考文章:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017328525009056
http://www.ruanyifeng.com/blog/2012/04/functional_programming.html