Python函數式編程-高階函數、匿名函數、裝飾器、偏函數

本篇文章我們來介紹下Python函數式編程的知識。最主要的一點,Python中的函數是對象,可以復制給變量!好了,我們來介紹幾個Python函數式編程中的要點,包括高階函數、匿名函數、裝飾器、偏函數等等。精彩內容,不容錯過!

1、高階函數

函數本身也可以賦值給變量,即:變量可以指向函數。如果一個變量指向了一個函數,那么,可以通過該變量來調用這個函數。

f = abs
f(-10)  # 10

既然變量可以指向函數,函數的參數能接收變量,那么一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數

def add_abs(x,y,f):
    return f(x) + f(y)
f = abs
add_abs(-5,6,f) # 11

接下來,我們介紹幾個重點的高階函數。

map函數

map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素,并把結果作為新的Iterator返回。我們之前介紹過了,Iterator是惰性序列,需要通過list()函數讓它把返回結果變為list。

def f(x):
    return x * x
r = list(map(f,[1,2,3,4,5,6,7,8,9,10]))
r

輸出結果為:

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

reduce函數

reduce把一個函數作用在一個序列[x1, x2, x3, ...]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算:

def f(x,y):
    return x + y
reduce(f,[1,2,3,4,5]) # 15

filter函數

和map()類似,filter()也接收一個函數和一個序列。和map()不同的是,filter()把傳入的函數依次作用于每個元素,然后根據返回值是True還是False決定保留還是丟棄該元素。filter()函數返回的是一個Iterator,也就是一個惰性序列,同樣需要通過list()函數讓它把返回結果變為list。

def is_odd(n):
    return n%2==1
list(filter(is_odd,[1,2,3,4,5,6,7,8,9]))

結果為:

[1, 3, 5, 7, 9]

sorted函數

sorted()函數也是一個高階函數,它還可以接收一個key函數來實現自定義的排序,例如按絕對值大小排序或者按照小寫字母順序進行排序:

print(sorted([36,5,-12,9,-21],key=abs))
print(sorted(['bob', 'about', 'Zoo', 'Credit'],key=str.lower))

結果為:

[5, 9, -12, -21, 36]
['about', 'bob', 'Credit', 'Zoo']

2、函數作為返回值

高階函數除了可以接受函數作為參數外,還可以把函數作為結果值返回。
我們來實現一個可變參數的求和,如果不需要立刻求和,而是在后面的代碼中,根據需要再計算怎么辦?可以不返回求和的結果,而是返回求和的函數:

def lazy_sum(*args):
    def sum():
        res = 0
        for n in args:
            res += n
        return res
    return sum

f = lazy_sum(1,3,5,7,9)
f()

在這個例子中,我們在函數lazy_sum中又定義了函數sum,
并且,內部函數sum可以引用外部函數lazy_sum的參數和局部變量,
當lazy_sum返回函數sum時,相關參數和變量都保存在返回的函數中,
這種稱為“閉包(Closure)”的程序結構擁有極大的威力。

請再注意一點,當我們調用lazy_sum()時,每次調用都會返回一個新的函數,即使傳入相同的參數:

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

另一個需要注意的問題是,返回的函數并沒有立刻執行,而是直到調用了f()才執行。我們來看一個例子:

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

f1,f2,f3 = count()
print(f1())
print(f2())
print(f3())

輸出結果為:

9
9
9

全部都是9!原因就在于返回的函數引用了變量i,但它并非立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,因此最終結果為9。如果一定要引用循環變量怎么辦?方法是再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量后續如何更改,已綁定到函數參數的值不變:

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

結果為:

1
4
9

3、匿名函數lambda

當我們在傳入函數時,有些時候,不需要顯式地定義函數,直接傳入匿名函數更方便。相信大家對于匿名函數一定不陌生,其實就是我們常說的lambda函數:

list(map(lambda x:x * x,[1,2,3,4,5,6,7,8,9]))

def build(x,y):
    return lambda:x * x + y * y

reduce(lambda x,y:x + y,[1,2,3,4,5,6,7,8,9])

4、裝飾器

在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
本質上,decorator就是一個返回函數的高階函數。
所以,假設我們要定義一個能夠打印當前函數名的decorator:

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

@log
def helloworld():
    print('hello world')

helloworld()

執行結果為:

call helloworld()
hello world

下面的例子中,正是由于wrapper中把 func(args,*kwargs)進行return,因此函數得以執行。

如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,寫出來會更復雜。比如,要自定義log的文本:


def log(text):
    def decorator(func):
        def wrapper(*args,**kwargs):
            print('%s %s():' % (text,func.__name__))
            return func(*args,**kwargs)
        return wrapper
    return decorator

@log('execute')
def helloworld():
    print('hello world')

helloworld()

上面代碼的執行過程相當于helloworld = log('execute')(helloworld)

我們講了函數也是對象,它有name等屬性,但你去看經過decorator裝飾之后的函數,它們的name已經從原來的'helloworld'變成了'wrapper':

helloworld.__name__  #'wrapper'

如果需要把原始函數的name等屬性復制到wrapper()函數中,使用Python內置的functools.wraps函數,代碼如下:

import functools

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

@log
def helloworld():
    print('hello world')

helloworld.__name__

此時的輸出就變為了'helloworld'

5、偏函數

一般的函數有許多需要定義的參數,假設我們想要固定其中的某些參數,返回一些新的函數,我們就可以使用functools.partial幫助我們創建一個偏函數,從而使得調用變得簡單

import functools
int2 = functools.partial(int, base=2)
int2('10010') # 18

當然我們也可以穿入一個函數字典:

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

當傳入的參數沒有對應的key時,它默認時作為*args的一部分自動加到左邊,因此下面的函數相當于比較max(10,5,6,7),返回10:

max2 = functools.partial(max, 10)
max2(5, 6, 7)
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容