Python:函數(shù)式編程

函數(shù)式編程就是一種抽象程度很高的編程范式蕴茴,純粹的函數(shù)式編程語言編寫的函數(shù)沒有變量,因此蓝翰,任意一個函數(shù),只要輸入是確定的,輸出就是確定的杖玲,這種純函數(shù)我們稱之為沒有副作用顿仇。而允許使用變量的程序設(shè)計(jì)語言,由于函數(shù)內(nèi)部的變量狀態(tài)不確定,同樣的輸入臼闻,可能得到不同的輸出鸿吆,因此,這種函數(shù)是有副作用的述呐。
函數(shù)式編程的一個特點(diǎn)就是惩淳,允許把函數(shù)本身作為參數(shù)傳入另一個函數(shù),還允許返回一個函數(shù)乓搬!
Python對函數(shù)式編程提供部分支持思犁。由于Python允許使用變量,因此进肯,Python不是純函數(shù)式編程語言激蹲。

高階函數(shù)

  • 變量可以指向函數(shù)
# 獲取絕對值
print(abs(-1))
# 可見,abs(-1)是函數(shù)調(diào)用江掩,而abs是函數(shù)本身学辱。
print(abs)
# 函數(shù)本身也可以賦值給變量,即:變量可以指向函數(shù)环形,變量f現(xiàn)在已經(jīng)指向了abs函數(shù)本身策泣。
# 直接調(diào)用abs()函數(shù)和調(diào)用變量f()完全相同。
f = abs
print(f(-1))

結(jié)果
1
<built-in function abs>
1
  • 函數(shù)名也是變量
    函數(shù)名其實(shí)就是指向函數(shù)的變量抬吟!對于abs()這個函數(shù)萨咕,完全可以把函數(shù)名abs看成變量,它指向一個可以計(jì)算絕對值的函數(shù)拗军!
abs = 10
print(abs)
print(abs(-1))

把a(bǔ)bs指向10后任洞,就無法通過abs(-10)調(diào)用該函數(shù)了!因?yàn)閍bs這個變量已經(jīng)不指向求絕對值函數(shù)而是指向一個整數(shù)10发侵!
當(dāng)然實(shí)際代碼絕對不能這么寫交掏,這里是為了說明函數(shù)名也是變量。要恢復(fù)abs函數(shù)刃鳄,請重啟Python交互環(huán)境盅弛。
注:由于abs函數(shù)實(shí)際上是定義在import builtins模塊中的,所以要讓修改abs變量的指向在其它模塊也生效叔锐,要用import builtins; builtins.abs = 10挪鹏。

  • 高階函數(shù)定義
    既然變量可以指向函數(shù),函數(shù)的參數(shù)能接收變量愉烙,那么一個函數(shù)就可以接收另一個函數(shù)作為參數(shù)讨盒,這種函數(shù)就稱之為高階函數(shù)。
# 以下就是高階函數(shù)
def test(x,y,f):
    return f(x)*f(y)

print(test(2,-9,abs))

# 結(jié)果
x = 2
y = -9
f = abs
f(x) * f(y) ==> abs(-5) * abs(6) ==> 11
return 18
map/reduce
  • map(fn,Iterable):map將傳入的函數(shù)依次作用到序列的每個元素步责,并把結(jié)果作為新的Iterator返回返顺。
def count(x):
    return x*x

# map()傳入的第一個參數(shù)是f禀苦,即函數(shù)對象本身。所以它也是高階函數(shù)遂鹊,由于結(jié)果r是一個Iterator振乏,
# Iterator是惰性序列,因此通過list()函數(shù)讓它把整個序列都計(jì)算出來并返回一個list秉扑。
print(list(map(count,[1,2,3,4,5,6,7,8,9])))

[1, 4, 9, 16, 25, 36, 49, 64, 81]
  • reduce(fn,Iterable):reduce把一個函數(shù)作用在一個序列[x1, x2, x3, ...]上慧邮,這個函數(shù)必須接收兩個參數(shù),reduce把結(jié)果繼續(xù)和序列的下一個元素做累積計(jì)算舟陆,其效果就是:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)误澳,如下例子,如何實(shí)現(xiàn)將一個str轉(zhuǎn)成int類型的函數(shù):
from functools import reduce

def fn(x,y):
    return x*10+y

def char2num(s):
    digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    return digits[s]

# map將'14789'轉(zhuǎn)成了分開的int類型的集合吨娜,reduce將分開的int集合合成可14789
print(reduce(fn,map(char2num,'14789'))) 

整合成一個函數(shù)就是:

from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def str2Int(s):
    def fn(x,y):
        return x*10 + y
    def char2Num(s):
        return DIGITS[s]
    return reduce(fn,map(char2Num,s))

print(str2Int('14789'))
filter

和map()類似脓匿,filter()也接收一個函數(shù)和一個序列。和map()不同的是宦赠,filter()把傳入的函數(shù)依次作用于每個元素陪毡,然后根據(jù)返回值是True還是False決定保留還是丟棄該元素。比如下面勾扭,我要刪除集合中的偶數(shù):

def dell(n):
    return n % 2 == 1

# filter()函數(shù)返回的是一個Iterator毡琉,也就是一個惰性序列,所以要強(qiáng)迫filter()完成計(jì)算結(jié)果妙色,需要用list()函數(shù)獲得所有結(jié)果并返回list桅滋。
print(list(filter(dell, [1, 2, 3, 4, 5, 6, 7, 8, 9])))

以下是利用Python的filter函數(shù)實(shí)現(xiàn)篩選素?cái)?shù)的埃氏篩法

# 構(gòu)建一個只會生成奇數(shù)的生成器,構(gòu)成的是無限序列
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

# 構(gòu)建一個篩選函數(shù),用于去除序列第一個數(shù)身辨,然后把序列第一個數(shù)的倍數(shù)給去除
def _not_divisible(n):
    return lambda x: x % n > 0

# 篩選素?cái)?shù)的生成器
def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一個數(shù)
        yield n
        it = filter(_not_divisible(n), it) # 篩選序列的素?cái)?shù)丐谋,然后構(gòu)造新序列

# 打印篩選出來的素?cái)?shù)
for n in primes():
    if n<100:
        print(n)
    else:
        break
sorted

說白了,就是給集合進(jìn)行排序的算法煌珊,看看以下幾個示例:

# 將集合按照絕對值排序
print(sorted([-1, 5, -3, 8, -37, -20, 10], key=abs))
# 默認(rèn)情況下号俐,字符串排序是按照首字母的ASCII的大小排序,小的在前定庵,大的在后
print(sorted(['hello', 'Case', 'about', 'Like']))
# 可以忽略首字母大小寫來排序
print(sorted(['hello', 'Case', 'about', 'Like'],key=str.lower))
# 將函數(shù)進(jìn)行反轉(zhuǎn)
print(sorted(['hello', 'Case', 'about', 'Like'], reverse=True))

返回函數(shù)

高階函數(shù)除了可以接受函數(shù)作為參數(shù)外吏饿,還可以把函數(shù)作為結(jié)果值返回,具體看示例:

# 定一個函數(shù)蔬浙,里面有一個求和函數(shù)猪落,并且將求和函數(shù)作為參數(shù)返回
def add(*agr):
    def sum():
        s = 0
        for n in agr:
            s = n + s
        return s
    return sum

# 調(diào)用add函數(shù),返回的是里面求和函數(shù)畴博,而不是求和結(jié)果
su = add(1,2,3,4,5,6)
# 這樣才是返回求和結(jié)果
print(su())
閉包

上面的返回函數(shù)的示例中笨忌,我們在函數(shù)add中又定義了函數(shù)sum,并且俱病,內(nèi)部函數(shù)sum可以引用外部函數(shù)add的參數(shù)和局部變量官疲,當(dāng)add返回函數(shù)sum時杂曲,相關(guān)參數(shù)和變量都保存在返回的函數(shù)中,這種就是稱為“閉包(Closure)”的程序結(jié)構(gòu)袁余。注意一點(diǎn),當(dāng)我們調(diào)用add()時咱揍,每次調(diào)用都會返回一個新的函數(shù)颖榜,即使傳入相同的參數(shù)。

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(), f2(), f3())

9 9 9

為啥結(jié)果和預(yù)料不一樣煤裙?應(yīng)該是1掩完,4,9才對硼砰,原因就在于返回的函數(shù)引用了變量i且蓬,但它并非立刻執(zhí)行。等到3個函數(shù)都返回時题翰,它們所引用的變量i已經(jīng)變成了3恶阴,因此最終結(jié)果為9。

記妆稀:返回的函數(shù)在其定義內(nèi)部引用了局部變量args冯事,所以,當(dāng)一個函數(shù)返回了一個函數(shù)后血公,其內(nèi)部的局部變量還被新函數(shù)引用昵仅,所以,需要注意的是返回的函數(shù)并沒有立刻執(zhí)行累魔,而是直到調(diào)用了f()才執(zhí)行摔笤。
返回閉包時牢記一點(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()

print(f1(), f2(), f3())

lambda(匿名函數(shù))

當(dāng)我們在傳入函數(shù)時,有些時候晚伙,不需要顯式地定義函數(shù)吮龄,直接傳入匿名函數(shù)更方便。關(guān)鍵字lambda表示匿名函數(shù)咆疗,冒號前面的x表示函數(shù)參數(shù)漓帚。匿名函數(shù)有個限制,就是只能有一個表達(dá)式午磁,不用寫return尝抖,返回值就是該表達(dá)式的結(jié)果毡们。

# 定義可一個函數(shù)f
def f(x):
    return x * x


# 傳入了一個匿名函數(shù),這個匿名函數(shù)等同于上面的f函數(shù)
print(list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])))

# 同樣匿名函數(shù)也是一個函數(shù)對象,可以賦值
f = lambda x: x * x
print(f(15))


# 當(dāng)然昧辽,也可以作為函數(shù)返回值
def build(x, y):
    return lambda: x * y

裝飾器

這個就好比裝飾設(shè)計(jì)模式衙熔,不改變原來函數(shù),但是給函數(shù)添加新的功能搅荞,而這種在代碼運(yùn)行期間動態(tài)增加功能的方式红氯,稱之為“裝飾器”(Decorator)。
比如我們定義一個簡單的打印hello的函數(shù):

def print_hello():
    print('hello')
    return

然后我們需要在不改變函數(shù)的代碼情況下咕痛,加上打印本函數(shù)名稱的方法痢甘,下面演示裝飾器的用法:

# 定義一個裝飾器decorator,本質(zhì)上,decorator就是一個返回函數(shù)的高階函數(shù)茉贡。所以塞栅,我們要定義一個能打印日志的decorator,可以定義如下
def log(func):
    def wrapper(*args, **kw):
        # func.__name__ 中__name__ 是函數(shù)對象的一個屬性腔丧,表示函數(shù)名字
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

# 我們要借助Python的@語法放椰,把decorator置于函數(shù)的定義處,
# 把@log()函數(shù)print_hello()的定義處愉粤,相當(dāng)于執(zhí)行了語句 print_hello = log(print_hello)
@log
def print_hello():
    print('hello')
    return

print_hello()

# 結(jié)果
call print_hello():
hello

由于log()是一個decorator庄敛,返回一個函數(shù),所以科汗,原來的print_hello()函數(shù)仍然存在藻烤,只是現(xiàn)在同名的print_hello變量指向了新的函數(shù),于是調(diào)用print_hello()將執(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ù)箭跳,可以這么寫:

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('執(zhí)行了')
def print_hello():
    print('hello')
    return

# 其實(shí)就是執(zhí)行了  print_hello = log('execute')(print_hello)
print_hello()

# 結(jié)果
執(zhí)行了 print_hello():
hello

首先執(zhí)行l(wèi)og('execute')晨另,返回的是decorator函數(shù),再調(diào)用返回的函數(shù)谱姓,參數(shù)是print_hello函數(shù)借尿,返回值最終是wrapper函數(shù)。以上兩種decorator的定義都沒有問題,但還差最后一步路翻。因?yàn)槲覀冎v了函數(shù)也是對象狈癞,它有name等屬性,但你去看經(jīng)過decorator裝飾之后的函數(shù)茂契,它們的name已經(jīng)從原來的'print_hello'變成了'wrapper'蝶桶。因?yàn)榉祷氐哪莻€wrapper()函數(shù)名字就是'wrapper',所以掉冶,需要把原始函數(shù)的name等屬性復(fù)制到wrapper()函數(shù)中莫瞬,否則,有些依賴函數(shù)簽名的代碼執(zhí)行就會出錯郭蕉。

如果你要解決這個問題,不需要編寫wrapper.name = func.name這樣的代碼喂江,Python內(nèi)置的functools.wraps就是干這個事的:

import functools

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

偏函數(shù)

偏函數(shù)的作用就是和我們講到召锈,通過設(shè)定參數(shù)的默認(rèn)值,可以降低函數(shù)調(diào)用的難度這一點(diǎn)作用差不多获询。比如如下函數(shù)涨岁,第二個參數(shù)默認(rèn)是10:

int('12345', base=8)

我們可以改變默認(rèn)參數(shù)來達(dá)到效果,但是如果我們要經(jīng)常調(diào)用int('12345', base=2)吉嚣,這樣感覺很麻煩梢薪,這個時候我們可以使用偏函數(shù)達(dá)到效果:

import functools

# functools.partial的作用就是,把一個函數(shù)的某些參數(shù)給固定壮⒍摺(也就是設(shè)置默認(rèn)值)秉撇,返回一個新的函數(shù),調(diào)用這個新函數(shù)會更簡單秋泄。
int2 = functools.partial(int, base=2)

print(int2('1010101'))
# 新的int2函數(shù)琐馆,僅僅是把base參數(shù)重新設(shè)定默認(rèn)值為2,但也可以在函數(shù)調(diào)用時傳入其他值
print(int2('1000000', base=10))

實(shí)際上可以接收函數(shù)對象恒序、args和kw這3個參數(shù)瘦麸。其實(shí)上面的操作,等同于改變*kw的操作歧胁。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滋饲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子喊巍,更是在濱河造成了極大的恐慌屠缭,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崭参,死亡現(xiàn)場離奇詭異勿她,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阵翎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門逢并,熙熙樓的掌柜王于貴愁眉苦臉地迎上來之剧,“玉大人,你說我怎么就攤上這事砍聊”臣冢” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵玻蝌,是天一觀的道長蟹肘。 經(jīng)常有香客問我,道長俯树,這世上最難降的妖魔是什么帘腹? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮许饿,結(jié)果婚禮上阳欲,老公的妹妹穿的比我還像新娘。我一直安慰自己陋率,他們只是感情好球化,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瓦糟,像睡著了一般筒愚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上菩浙,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天巢掺,我揣著相機(jī)與錄音,去河邊找鬼劲蜻。 笑死址遇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斋竞。 我是一名探鬼主播倔约,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坝初!你這毒婦竟也來了浸剩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤鳄袍,失蹤者是張志新(化名)和其女友劉穎绢要,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拗小,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡重罪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剿配。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡搅幅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呼胚,到底是詐尸還是另有隱情茄唐,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布蝇更,位于F島的核電站沪编,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏年扩。R本人自食惡果不足惜蚁廓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厨幻。 院中可真熱鬧相嵌,春花似錦、人聲如沸克胳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漠另。三九已至,卻和暖如春跃赚,著一層夾襖步出監(jiān)牢的瞬間笆搓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工纬傲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留满败,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓叹括,卻偏偏與公主長得像算墨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子汁雷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

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