函數(shù)式編程--四個函數(shù)

一净嘀、返回函數(shù)

函數(shù)作為返回值

高階函數(shù)除了可以接收函數(shù)作為參數(shù)外狂魔,還可以把函數(shù)作為結(jié)果值返回。我們來實(shí)現(xiàn)一個可變參數(shù)的求和凌彬。通常求和函數(shù)是這樣定義的:

>>> def clac_sum(*args):
...     ax = 0
...     for i in args:
...             ax = ax + n
...     return ax
...

但是沸柔,如果不需要立刻求和,而是在后面的代碼中铲敛,根據(jù)需要在計(jì)算怎么辦褐澎?可以不返回求和的結(jié)果,而是返回求和的函數(shù):

>>> def lazy_sum(*args):
...     def sum():
...             ax = 0
...             for n in args:
...                     ax = ax + n
...             return ax
...     return sum

當(dāng)我們調(diào)用lazy_sum()時伐蒋,返回的并不是求和結(jié)果工三,而是求和函數(shù):

>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>

調(diào)用函數(shù)f時迁酸,才真正計(jì)算求和的結(jié)果:

>>> f()
25

在這個例子中,我們在函數(shù)lazy_sum中定義了函數(shù)sum俭正,并且內(nèi)部函數(shù)sum可以引用外部函數(shù)lazy_sum的參數(shù)和局部變量奸鬓,當(dāng)lazy_sum返回函數(shù)sum時,相關(guān)參數(shù)和變量都保存在返回的函數(shù)中段审,這種稱為“閉包(Closure)”的程序結(jié)構(gòu)擁有極大的威力全蝶。
請?jiān)谧⒁庖稽c(diǎn),當(dāng)我們調(diào)用lazy_sum()時寺枉,每次調(diào)用都會返回一個新的函數(shù)抑淫,即使傳入相同的參數(shù):

>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1 == f2
False

f1()f2()調(diào)用結(jié)果互不影響。

閉包

注意到返回的函數(shù)在其定義內(nèi)部引用了局部變量args姥闪,所以始苇,當(dāng)一個函數(shù)返回了一個函數(shù)后,其內(nèi)部的局部變量還被新函數(shù)引用筐喳,所以催式,閉包用起來簡單,實(shí)現(xiàn)起來可不容易避归。
另一個需要注意的問題是荣月,返回的函數(shù)并沒有立即執(zhí)行,而是知道調(diào)用了f()才執(zhí)行梳毙。我們來看一個例子:

>>> def count():
...     fs = []
...     for i in range(1, 4):
...             def f():
...                     return i * i
...             fs.append(f)
...     return fs
...
>>> f1, f2, f3 = count()
>>> f1()
9
>>> f2()
9
>>> f3()
9

全部都是9哺窄!原因就在于返回的函數(shù)引用了變量i,但他并非立即執(zhí)行账锹。等到3個函數(shù)都返回時萌业,他們所引用的變量i已經(jīng)變成了3,因此最終結(jié)果是9奸柬。
返回閉包時牢記一點(diǎn):返回函數(shù)不要引用任何循環(huán)變量生年,或者后續(xù)會發(fā)生變化的變量
如果一定要引用循環(huán)變量怎么辦?方法是再創(chuàng)建一個函數(shù)廓奕,用該函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值抱婉,無論該循環(huán)變量后續(xù)如何更改,已綁定到函數(shù)參數(shù)的值不變:

>>> def count():
...     def f(j):
...             def g():
...                     return j * j
...             return g
...     fs = []
...     for i in range(1, 4):
...             fs.append(f(i))
...     return fs
...
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

二懂从、匿名函數(shù)

當(dāng)我們在傳入函數(shù)時授段,有些時候,不需要顯式地定義函數(shù)番甩,直接傳入匿名函數(shù)更方便。在Python中届搁,對匿名函數(shù)提供了有限支持缘薛。還是以map()函數(shù)為例窍育,計(jì)算f(x)=x2時,除了定義一個f(x)的函數(shù)外宴胧,還可以直接傳入匿名函數(shù):

>>> list(map(lambda x : x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

通過對比就可以看出漱抓,匿名函數(shù)lambda x : x * x實(shí)際上就是

def f(x):
    return x * x

關(guān)鍵字lambda表示匿名函數(shù),冒號前面的x表示函數(shù)參數(shù)恕齐。
匿名函數(shù)有個限制乞娄,就是只能有一個表達(dá)式,不用寫return显歧,返回值就是該表達(dá)式的結(jié)果仪或。用匿名函數(shù)有個好處,因?yàn)楹瘮?shù)沒有名字士骤,不必?fù)?dān)心函數(shù)名沖突范删。此外,匿名函數(shù)也是一個函數(shù)對象拷肌,也可以把匿名函數(shù)賦值給一個變量到旦,再利用變量來調(diào)用該函數(shù):

>>> f = lambda x : x * x
>>> f
<function <lambda> at 0x00000279E289C1E0>
>>> f(5)
25
# 作為返回值返回
>>> def build(x, y):
...     return lambda : x * x + y * y

三、裝飾器

由于函數(shù)也是一個對象巨缘,而且函數(shù)對象可以被賦值給變量添忘,所以,通過變量也能調(diào)用該函數(shù)若锁。

>>> def now():
...     print('2019-5-13')
...
>>> f = now
>>> f()
2015-3-25

函數(shù)對象有一個__name__屬性搁骑,可以拿到函數(shù)的名字:

>>> now.__name__
'now'
>>> f.__name__
'now'

現(xiàn)在,假設(shè)我們要增強(qiáng)now()函數(shù)的功能拴清,比如靶病,在函數(shù)調(diào)用前后自動打印日志,但又不希望修改now()函數(shù)的定義口予,這種在代碼運(yùn)行期間動態(tài)添加功能的方式娄周,稱之為“裝飾器”(Decorator)。
本質(zhì)上沪停,decorator就是一個返回函數(shù)的高階函數(shù)煤辨。所以,我們要定義一個能打印日志的decorator木张,定義如下:

>>> def log(func):
...     def wrapper(*args, **kw):
...             print('call %s():' % func.__name__)
...             return func(*args, **kw)
...     return wrapper
...

觀察上面的log众辨,因?yàn)樗且粋€decorator,所以接受一個函數(shù)作為參數(shù)舷礼,并返回一個函數(shù)鹃彻。我們要借助Python的@語法,把decorator置于函數(shù)的定義處:

>>> @log
... def now():
...     print('2019-5-13')
...
>>> now()
call now():
2019-5-13

@log放到now()函數(shù)的定義處妻献,想但與執(zhí)行了語句:

now = log(now)

由于log()是一個decorator蛛株,返回一個函數(shù)团赁,所以,原來的now()函數(shù)依然存在谨履,只是現(xiàn)在同名的now變量指向了新的函數(shù)欢摄,于是調(diào)用now()將執(zhí)行新函數(shù),即在log()函數(shù)中返回的wrapper()函數(shù)笋粟。
wrapper()函數(shù)的參數(shù)定義是(*args, **kw)怀挠,因此,wrapper()函數(shù)可以接受任意參數(shù)的調(diào)用害捕。在wrapper()函數(shù)內(nèi)绿淋,首先打印日志,在緊接著調(diào)用原始函數(shù)吨艇。
如果decorator本身需要傳入?yún)?shù)躬它,那就需要編寫一個返回decorator的高階函數(shù),寫出來會更復(fù)雜东涡。比如要定義log的文本:

>>> def log(text):
...     def decorator(func):
...             def wrapper(*args, **kw):
...                     print('%s %s():' % (text, func.__name__))
...                     return func(*args, **kw)
...             return wrapper
...     return decorator
...
# 用法
>>> @log('hello')
... def now():
...     print('2019-5-13')
...
# 執(zhí)行結(jié)果
>>> now()
hello now():
2019-5-13

和兩層嵌套的decorator相比冯吓,3層嵌套的效果是這樣的:

>>> now = log('hello')(now)

我們來剖析上面的語句,首先執(zhí)行log('hello')疮跑,返回的是decorator函數(shù)组贺,在調(diào)用返回的函數(shù),參數(shù)是now函數(shù)的祖娘,返回值最終是wrapper函數(shù)失尖。
以上兩種decorator的定義都沒有問題,但還差最后一步渐苏。因?yàn)槲覀冎v了函數(shù)也是對象掀潮,他有__name__等屬性,但你去看經(jīng)過decorator裝飾之后的函數(shù)琼富,他們的__name__已經(jīng)從原來的now變成了wrapper:

>>> now.__name__
'wrapper'

因?yàn)榉祷氐哪莻€wrapper()函數(shù)名字就是wrapper仪吧,所以,需要把原始函數(shù)的__nane__等屬性復(fù)制到'wrapper()'函數(shù)中鞠眉,否則薯鼠,有些依賴函數(shù)簽名的代碼執(zhí)行就會出錯。不需要編寫wrapper.__name__ = func.__name__這樣的代碼械蹋,Python內(nèi)置的functools.wraps就是干這個事的出皇,所以,一個完整的decorator的寫法如下:

>>> import functools
>>> def log(func):
...     @functools.wraps(func)
...     def wrapper(*args, **kw):
...             print('call %s():' % func.__name__)
...             return func(*args, **kw)
...     return wrapper
...

四哗戈、偏函數(shù)

Python的functools模塊提供了很多有用的功能郊艘,其中一個就是偏函數(shù)(Partial function)。要注意,這里的偏函數(shù)和數(shù)學(xué)意義上的偏函數(shù)不一樣暇仲。在介紹函數(shù)參數(shù)的時候步做,我們講到副渴,通過設(shè)定參數(shù)的默認(rèn)值奈附,可以降低函數(shù)的難度。而偏函數(shù)也可以做到這一點(diǎn)煮剧。例子:
int()函數(shù)可以把字符串轉(zhuǎn)換為整數(shù)斥滤,當(dāng)傳入字符串時,int()函數(shù)默認(rèn)按十進(jìn)制轉(zhuǎn)換:

>>> int('12345')
12345

但是int()函數(shù)還提供額外的base參數(shù)勉盅,默認(rèn)值為10佑颇,如果傳入base參數(shù),就可以做N進(jìn)制的轉(zhuǎn)換:

>>> int('12345', base=8)
5349
>>> int('12345', base=16)
74565

假設(shè)要轉(zhuǎn)換大量的二進(jìn)制字符串草娜,每次傳入int(x, base=2)非常麻煩挑胸,于是我們想到,可以定義一個int2()函數(shù)宰闰,默認(rèn)把base=2穿進(jìn)去:

>>> def int2(x, base=2):
...     return int(x, base)
...

這樣我們轉(zhuǎn)換二進(jìn)制就非常方便了:

>>> int2('1000000')
64
>>> int2('1010101')
85

functools.partial就是幫助我們創(chuàng)建一個偏函數(shù)的茬贵,不需要我們自己定義int2(),可以直接使用下面的代碼創(chuàng)建一個新的函數(shù)int2

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

所以簡單總結(jié)functools.partial的作用就是移袍,把一個函數(shù)的某些參數(shù)給固定捉庠濉(也就是設(shè)置默認(rèn)值),返回一個新的函數(shù)葡盗,調(diào)用這個新函數(shù)會更簡單螟左。
注意到上面的int2函數(shù),僅僅是把base參數(shù)重新設(shè)定默認(rèn)值為2觅够,但也可以在函數(shù)調(diào)用時傳入其他值:

>>> int2('1000000', base=10)
1000000

最后胶背,創(chuàng)建偏函數(shù)的時候,實(shí)際可以接收函數(shù)對象喘先、args**kw這3個參數(shù)钳吟,當(dāng)傳入:

int2 = functools.partial(int, base=2)

實(shí)際上固定了int()函數(shù)的關(guān)鍵字參數(shù)base,也就是:

int2('10010')

相當(dāng)于:

kw = {'base' : 2}
int('10010', **kw)

當(dāng)傳入:

max2 = functools.partial(max, 10)

實(shí)際上會把10作為*args的一部分自動加到左邊苹祟,也就是:

max2(5, 6, 7)

相當(dāng)于:

args = (10, 5, 6, 7)
max(*args)

結(jié)果為10

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末砸抛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子树枫,更是在濱河造成了極大的恐慌直焙,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砂轻,死亡現(xiàn)場離奇詭異奔誓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進(jìn)店門厨喂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來和措,“玉大人,你說我怎么就攤上這事蜕煌∨哨澹” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵斜纪,是天一觀的道長贫母。 經(jīng)常有香客問我,道長盒刚,這世上最難降的妖魔是什么腺劣? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮因块,結(jié)果婚禮上橘原,老公的妹妹穿的比我還像新娘。我一直安慰自己涡上,他們只是感情好趾断,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吓懈,像睡著了一般歼冰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耻警,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天隔嫡,我揣著相機(jī)與錄音,去河邊找鬼甘穿。 笑死腮恩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的温兼。 我是一名探鬼主播秸滴,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼募判!你這毒婦竟也來了荡含?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤届垫,失蹤者是張志新(化名)和其女友劉穎释液,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體装处,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡误债,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寝蹈。...
    茶點(diǎn)故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡李命,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出箫老,到底是詐尸還是另有隱情封字,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布槽惫,位于F島的核電站周叮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏界斜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一合冀、第九天 我趴在偏房一處隱蔽的房頂上張望各薇。 院中可真熱鬧,春花似錦君躺、人聲如沸峭判。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽林螃。三九已至,卻和暖如春俺泣,著一層夾襖步出監(jiān)牢的瞬間疗认,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工伏钠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留横漏,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓熟掂,卻偏偏與公主長得像缎浇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赴肚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評論 2 354

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

  • 直接上正文 函數(shù)是Python內(nèi)建支持的一種封裝誉券,我們通過把大段代碼拆成函數(shù)指厌,通過一層一層的函數(shù)調(diào)用,就可以把復(fù)雜...
    OzanShareing閱讀 448評論 0 0
  • 文章來源 基本是拷貝的內(nèi)容横朋,目的是為了將知識點(diǎn)整理在一起仑乌。除了一個小結(jié)(裝飾器部分)提供了我自己的解法,其余基本沒...
    王詩翔閱讀 632評論 0 1
  • 函數(shù)式編程就是一種抽象程度很高的編程范式,純粹的函數(shù)式編程語言編寫的函數(shù)沒有變量晰甚,因此衙传,任意一個函數(shù),只要輸入是確...
    齊天大圣李圣杰閱讀 1,529評論 0 2
  • 函數(shù)是Python內(nèi)建支持的一種封裝厕九,我們通過把大段代碼拆成函數(shù)蓖捶,通過一層一層的函數(shù)調(diào)用,就可以把復(fù)雜任務(wù)分解成簡...
    祐吢房_2c9a閱讀 391評論 1 1
  • 今天是軍訓(xùn)的第二天扁远,蔡教官又來到我們班了俊鱼,講了些注意事項(xiàng),然后我們?nèi)サ厣系囊黄盏爻┞颉P液梦覀兪窃跇涫a底下練習(xí)并闲,...
    林綺馨閱讀 193評論 0 2