Python函數(shù)式編程

函數(shù)式編程

函數(shù)式編程就是一種給抽象程度很高的編程范式,純粹的函數(shù)式編程語(yǔ)言編寫(xiě)的函數(shù)沒(méi)有變量腊敲,因此击喂,任意一個(gè) 函數(shù),只要輸入是正確的碰辅,輸出就是正確的懂昂。而允許使用變量的程序設(shè)計(jì)語(yǔ)言,由于函數(shù)內(nèi)部的變量狀態(tài)不確定没宾,同樣的輸入凌彬,可能得到不同的輸出。
函數(shù)式編程的一個(gè)特點(diǎn)就是循衰,允許把函數(shù)本身作為參數(shù)傳入另一個(gè)函數(shù)铲敛,還允許返回一個(gè)函數(shù)。

高階函數(shù)
變量可以指向函數(shù)

即:

>>> abs(-10)
10
>>> abs
<built-in function abs>
>>> x = abs(-10)
>>> x
10
>>> f = abs
>>> f
<built-in function abs>
#即:
>>> f(-10)
10
函數(shù)名也是變量

即:

>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

abs指向10后会钝,就無(wú)法通過(guò)abs(-10)調(diào)用該函數(shù)了伐蒋,因?yàn)?code>abs這個(gè)變量已經(jīng)不指向求絕對(duì)值函數(shù)而是指向一個(gè)整數(shù)10,要恢復(fù)abs函數(shù)迁酸,請(qǐng)重啟Python交互環(huán)境

傳入函數(shù)

既然變量可以指向函數(shù)先鱼,函數(shù)的參數(shù)能接收變量,那么一個(gè)函數(shù)就可以接收另一個(gè)函數(shù)作為參數(shù)奸鬓,這種函數(shù)稱(chēng)為高階函數(shù)焙畔。一個(gè)簡(jiǎn)單的高階函數(shù)

def add(x, y ,f)
    return f(x) + f(y)
map/reduce

python內(nèi)建了map()reduce()函數(shù)
map()函數(shù)接收臉啊哥哥參數(shù),一個(gè)是函數(shù)串远,一個(gè)是Iterable宏多,map將傳入的函數(shù)一次作用到序列的每個(gè)元素,并把結(jié)果作廢新的Iterator返回抑淫。即:

def f(x):
    return x ** 2
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(r))    #[1, 4, 9, 16, 25, 36, 49, 64, 81]

map傳入的第一個(gè)參數(shù)是f绷落,即函數(shù)對(duì)象本身,由于結(jié)果r是一個(gè)Iterator始苇,Iterator是惰性序列砌烁,因此通過(guò)list()函數(shù)把整個(gè)序列都計(jì)算出來(lái)并返回一個(gè)list

map()作為高階函數(shù),事實(shí)上它把運(yùn)算規(guī)則抽象化,作用在Iterable的每一個(gè)元素并生成一個(gè)新的Iterator
reduce()``函數(shù)函喉,把一個(gè)函數(shù)作用在一個(gè)序列上避归,這個(gè)函數(shù)必須接收兩個(gè)參數(shù),reduce`把結(jié)果繼續(xù)和序列的洗一個(gè)元素做累積計(jì)算管呵,其效果是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2,), x3), x4)
#對(duì)一個(gè)序列求和
>>> from functools import reduce
>>> def add(x, y)
        return x + y
>>> reduce(add, [1, 3, 5, 7, 9])
25
filter

filter()函數(shù)用于過(guò)濾序列和map()相似梳毙,filter()也接收一個(gè)函數(shù)和一個(gè)序列。和map()不同的是捐下,filter()把純?nèi)绲暮瘮?shù)依次作用于每個(gè)元素账锹,然后根據(jù)返回值的TrueFalse決定保留還是丟棄該元素,例:保留奇數(shù)

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]

可看出坷襟,上述舉例中is_odd()可以看作是一個(gè)篩選函數(shù)奸柬。使用filter()函數(shù),關(guān)鍵在于正確實(shí)現(xiàn)一個(gè)篩選函數(shù)婴程。

計(jì)算素?cái)?shù)

計(jì)算素?cái)?shù)的一個(gè)方法是埃拉托色尼篩選法廓奕,理解非常簡(jiǎn)單:
列出從2開(kāi)始的所有自然數(shù),構(gòu)造一個(gè)序列:
2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,......
取序列的第一個(gè)數(shù)2档叔,它一定是素?cái)?shù)桌粉,然后用2把序列的2的倍數(shù)篩掉
3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,......
取序列的第一個(gè)數(shù)3,它一定是素?cái)?shù)衙四,然后用3把序列的3的倍數(shù)篩掉
4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,......
取序列的第一個(gè)數(shù)5铃肯,它一定是素?cái)?shù),然后用5把序列的5的倍數(shù)篩掉
6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,......
不斷的篩選下去传蹈,就可以得到所有的素?cái)?shù)缘薛。即:
構(gòu)造一個(gè)從3開(kāi)始的奇數(shù)序列,下列是一個(gè)從3開(kāi)始的奇數(shù)序列生成器卡睦,并且是一個(gè)無(wú)限序列3-∞

def _iter():
    n = 1
    while True:
         n = n + 2
        yield n

定義一個(gè)篩選函數(shù)

def _not_divisible(n):
    return lambda x : x % n > 0    #序列x中的所有元素% n > 0的保留,% n == 0的不保留

最后漱抓,定義一個(gè)生成器表锻,不斷返回下一個(gè)素?cái)?shù)

def primes():
    yield 2
    it = _iter()    # 初始化序列
    while True:
        n = next(it)    # 返回序列的第一個(gè)數(shù)
        yield n
        it = filter(_not_divisible(n), it)    # 構(gòu)造一個(gè)新的序列

primes是一個(gè)無(wú)限序列,所以調(diào)用時(shí)乞娄,設(shè)置一個(gè)退出循環(huán)的條件

for n in primes():
    if n < 100:
        print(n)    #打印從2-100的所有素?cái)?shù)值瞬逊,即上述`primes()`函數(shù)中`yield`返回的n
    else:
        break
sorted排序算法

內(nèi)置的排序算法函數(shù)sorted(),可以對(duì)list進(jìn)行排序仪或。sorted()函數(shù)還可以接收一個(gè)key函數(shù)來(lái)實(shí)現(xiàn)自定義的排序确镊。即:

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
# 按學(xué)生姓名進(jìn)行排序規(guī)則
def by_name_sort(t):
    return t[0]
# 按照學(xué)生成績(jī)從高到低排序規(guī)則
def by_score_sort(t):
    return -t[1]

# 運(yùn)行結(jié)果:
L2 = sorted(L, key=by_name_sort)
print(L2)
# 結(jié)果:[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)] 
L3 = sorted(L, key=by_score_sort)
print(L3)
# 結(jié)果:[('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)] 
返回函數(shù)

將函數(shù)作為結(jié)果值返回:

def lazy_sum(*args) :
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum    # 返回sum函數(shù),sum函數(shù)內(nèi)暫不計(jì)算求和
f = lazy_sum(1, 3, 5, 7, 9)
print(f)
# TODO:打臃渡尽:<function lazy_sum.<locals>.sum at 0x0000014B7A247A60>
print(f())    # 運(yùn)行f函數(shù)蕾域,即lazy_sum(*args)返回的sum()函數(shù)
# 打印結(jié)果:25

函數(shù)lazy_sum(*args)中又定義了函數(shù)sum,內(nèi)部函數(shù)sum可以引用外部函數(shù)lazy_sum的參數(shù)和局部變量,當(dāng)lazy_sum返回函數(shù)sum時(shí)旨巷,相關(guān)參數(shù)和變量都保存在返回的函數(shù)中巨缘,被稱(chēng)為“閉包”的程序擁有極大的威力。
注:每次調(diào)用時(shí)采呐,都會(huì)返回一個(gè)新的函數(shù)若锁,即使傳入相同的參數(shù)

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

返回的函數(shù)并沒(méi)有立刻執(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()
# 調(diào)用
print(f1)    #    9
print(f2)    #    9
print(f3)    #    9

全部都是9斧吐,原因在于返回的函數(shù)引用了變量i又固,但是并非立刻執(zhí)行,等到3個(gè)函數(shù)都返回時(shí)煤率,引用的變量i已經(jīng)變成了3仰冠,因那次結(jié)果為9

返回閉包時(shí)牢記:返回函數(shù)不要引用任何循環(huán)變量涕侈,或者后續(xù)會(huì)發(fā)生變化的變量沪停。
若一定要引用循環(huán)變量,在創(chuàng)建一個(gè)函數(shù)裳涛,用函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值木张。

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被執(zhí)行,因此i的當(dāng)前值被傳入f()
    return fs

f1, f2, f3 = count()
# 調(diào)用
print(f1)    #    1
print(f2)    #    4
print(f3)    #    9

缺點(diǎn)是代碼較長(zhǎng)端三,可利用lambda函數(shù)縮短代碼舷礼。

匿名函數(shù)lambda

lambda x: x * x實(shí)際上是:

def f(x):
    return x * x

lambda是匿名函數(shù)的關(guān)鍵字,冒號(hào)前面的x表示函數(shù)參數(shù)郊闯。

匿名函數(shù)有個(gè)限制妻献,只能有一個(gè)表達(dá)式,不用寫(xiě)return团赁,返回值就是該表達(dá)式的結(jié)果育拨。也可以將匿名函數(shù)賦值給一個(gè)變量,利用變量來(lái)調(diào)用該函數(shù)

裝飾器

函數(shù)也是一個(gè)對(duì)象欢摄,而且函數(shù)對(duì)象可以被賦值給變量熬丧,所以,通過(guò)變量也能調(diào)用該函數(shù)怀挠。函數(shù)對(duì)象有一個(gè)__name__屬性析蝴,可以拿到函數(shù)的沒(méi)名字:

def now():
    print('2020-06-05')
f = now
print(now.__name__)    # 'now'

現(xiàn)增強(qiáng)now()函數(shù)的功能,如绿淋,在函數(shù)調(diào)用前后自動(dòng)打印日志闷畸,但是不希望修改now()函數(shù)的定義芳悲,這種在代碼運(yùn)行期間動(dòng)態(tài)增加功能的方式乎完,被稱(chēng)為“裝飾器”(Decorator)。

decorator是一個(gè)返回函數(shù)的高階函數(shù)忍疾,定義一個(gè)能打印日志的decorator

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

函數(shù)log是一個(gè)decorator,所以接受一個(gè)函數(shù)作為參數(shù)倘待,并返回一個(gè)函數(shù)疮跑。借助@語(yǔ)法,把dedcorator置于函數(shù)的定義處:

@log
def now():
    print('2020-06-05')

當(dāng)調(diào)用now()函數(shù)凸舵,不僅會(huì)運(yùn)行now()函數(shù)本身祖娘,還會(huì)在運(yùn)行now()函數(shù)前打印一行日志:

>>> now()
calll now()
2020-06-05

@log放在now()函數(shù)的定義處,相當(dāng)于執(zhí)行了語(yǔ)句now = log(now)

由于log()是一個(gè)decorator啊奄,返回一個(gè)函數(shù)渐苏,所以,原來(lái)的now()函數(shù)任然存在菇夸,只是同名的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ù)哗戈,就需要編寫(xiě)一個(gè)返回decorator的高階函數(shù),寫(xiě)出來(lái)會(huì)更加復(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('execute')
def now():
    print('2020-06-05')

now()
# execute now():
# 2020-06-05

3層嵌套的效果是這樣的唯咬,now = log('execute')(now)。首先執(zhí)行log('execute')畏浆,返回的是decorator函數(shù)胆胰,再調(diào)用返回的函數(shù),參數(shù)是now函數(shù)刻获,返回值最終是wrapper函數(shù)煮剧。

函數(shù)也是對(duì)象,它有__name__等屬性将鸵,經(jīng)過(guò)哦decorator裝飾之后,'__name__'屬性從原來(lái)的函數(shù)名變成了'wrapper'佑颇。因?yàn)榉祷氐哪莻€(gè)wrapper()函數(shù)名字就是wrapper顶掉,所以,幼把原始函數(shù)的__name__等屬性復(fù)制到wrapper()函數(shù)中挑胸,否則痒筒,有些依賴(lài)函數(shù)簽名的代碼執(zhí)行會(huì)出錯(cuò)。

python中內(nèi)置了funtools.wraps,就是干這個(gè)事情的:

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

import functools是導(dǎo)入functools模塊簿透。

偏函數(shù)

偏函數(shù)移袍,即functools模塊中的一個(gè)功能函數(shù)partialfunctools.partial的作用就是老充,把一個(gè)函數(shù)的某些參數(shù)給固定住(即默認(rèn)值)葡盗,返回一個(gè)新的函數(shù),調(diào)用這個(gè)新函數(shù)會(huì)更加簡(jiǎn)單啡浊。

當(dāng)函數(shù)的參數(shù)太多觅够,需要簡(jiǎn)化時(shí),使用functools.partial創(chuàng)建一個(gè)新的函數(shù)巷嚣,新函數(shù)可以固定原函數(shù)部分參數(shù)喘先,從而調(diào)用時(shí)更簡(jiǎn)單。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載廷粒,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者窘拯。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坝茎,隨后出現(xiàn)的幾起案子涤姊,更是在濱河造成了極大的恐慌,老刑警劉巖景东,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砂轻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡斤吐,警方通過(guò)查閱死者的電腦和手機(jī)搔涝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)和措,“玉大人庄呈,你說(shuō)我怎么就攤上這事∨哨澹” “怎么了诬留?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)贫母。 經(jīng)常有香客問(wèn)我文兑,道長(zhǎng),這世上最難降的妖魔是什么腺劣? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任绿贞,我火速辦了婚禮,結(jié)果婚禮上橘原,老公的妹妹穿的比我還像新娘籍铁。我一直安慰自己涡上,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布拒名。 她就那樣靜靜地躺著吩愧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪增显。 梳的紋絲不亂的頭發(fā)上雁佳,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音甸怕,去河邊找鬼甘穿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梢杭,可吹牛的內(nèi)容都是我干的温兼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼武契,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼募判!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起咒唆,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤届垫,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后全释,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體装处,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年浸船,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妄迁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡李命,死狀恐怖登淘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情封字,我是刑警寧澤黔州,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站阔籽,受9級(jí)特大地震影響流妻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笆制,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一绅这、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧项贺,春花似錦君躺、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至奕删,卻和暖如春俺泣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背完残。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工伏钠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谨设。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓熟掂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親扎拣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赴肚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345