Python3(5) Python 函數(shù)式編程

本系列主要學(xué)習(xí)Python的基本使用和語法知識,后續(xù)可能會(huì)圍繞著AI學(xué)習(xí)展開。
Python3 (1) Python語言的簡介
Python3 (2) Python語法基礎(chǔ)
Python3 (3) Python函數(shù)
Python3 (4) Python高級特性
Python3(5) Python 函數(shù)式編程
Python支持函數(shù)式編程,允許把函數(shù)本身作為參數(shù)傳入另一個(gè)函數(shù),還允許返回一個(gè)函數(shù)!

高階函數(shù)

對于一直在java中學(xué)習(xí)的人來說,高階函數(shù)還是一個(gè)陌生、高大上的名詞,它有三個(gè)特點(diǎn):

  • 變量可以指向函數(shù)
  • 函數(shù)名也是變量
  • 函數(shù)可以作為參數(shù)傳入

所以高階函數(shù)的定義:把函數(shù)作為參數(shù)傳入,這樣的函數(shù)稱為高階函數(shù),函數(shù)式編程就是指這種高度抽象的編程范式。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def add(x, y, f):
    return f(x) + f(y)

print('|x|+|y| = ',add(-5, 6, abs))

輸出結(jié)果:

|x|+|y| =  11

幾個(gè)內(nèi)置高階函數(shù)

map/reduce

首先聲明map函數(shù)與java中的map是兩個(gè)名詞,沒有關(guān)聯(lián)。map高階函數(shù)的定義:map()函數(shù)接收兩個(gè)參數(shù),一個(gè)是函數(shù),一個(gè)是Iterable,map將傳入的函數(shù)依次作用到序列的每個(gè)元素,并把結(jié)果作為新的Iterator返回。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

L = list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
print(L)

輸出結(jié)果:

['1', '2', '3', '4', '5', '6', '7', '8', '9']

將list中的元素轉(zhuǎn)換成字符串。

reduce把一個(gè)函數(shù)作用在一個(gè)序列[x1, x2, x3, ...]上,這個(gè)函數(shù)必須接收兩個(gè)參數(shù),reduce把結(jié)果繼續(xù)和序列的下一個(gè)元素做累積計(jì)算。reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import reduce
def fn(x,y):
    return x*10+y

L = reduce(fn, [1,2,3,4,5])

print(L)

輸出結(jié)果:

12345

用list 實(shí)現(xiàn)一個(gè)按list序列生成一個(gè)整數(shù),它的結(jié)果就是一個(gè)最終的數(shù)。

使用map 和 reduce 寫一個(gè)字符串轉(zhuǎn)整數(shù)的函數(shù)、字符串轉(zhuǎn)浮點(diǎn)數(shù)的函數(shù):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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 char2num(s):
    return DIGITS[s]
def str2int(s):
    def fn(x, y):
        return x * 10 + y
    return reduce(fn, map(char2num, s))
def str2float(s):
    n = s.index('.')
    return reduce(lambda x,y:x*10+y,map(char2num,s[:n]+s[n+1:]))/10**n

print(str2int('10001'))
print(str2float('10001.0001'))

輸出結(jié)果:

10001
1000.10001

filter

用于過濾序列,filter 與map定義的格式相同,參數(shù)接受一個(gè)函數(shù),一個(gè)序列,返回一個(gè)Iterator。filter通過判斷函數(shù)的返回值是否為True來丟棄一些元素。

filter的主要應(yīng)用是實(shí)現(xiàn)一個(gè)篩選函數(shù):我們來實(shí)現(xiàn)一個(gè)素?cái)?shù)的序列

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

def _not_divisible(n):
    return lambda x: x % n > 0

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

# 打印1000以內(nèi)的素?cái)?shù):
for n in primes():
    if n < 20:
        print(n)
    else:
        break

輸出結(jié)果:

2
3
5
7
11
13
17
19

實(shí)現(xiàn)原理,依次將3,5,7,9... 的倍數(shù)篩選完,最終剩下的為素?cái)?shù)。
練習(xí)一個(gè)回?cái)?shù)的篩選:從左向右讀和從右向左讀都是一樣的數(shù)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def is_palindrome(n):
   return str(n) == str(n)[::-1]
# 測試:
output = filter(is_palindrome, range(1, 200))
print('1~200:', list(output))

輸出結(jié)果:

1~200: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]

sorted

sorted 排序高階函數(shù),它的使用非常靈活,可以傳入自定義的排序函數(shù)、反向排序,在復(fù)雜的排序中核心代碼還是非常的簡潔。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

L = [3,1,-5,-2,9]
#普通的用法
print(sorted(L))
#反向排序
print(sorted(L,reverse=True))
K = ['Apple','banana','Pear','tomato']
print(sorted(K))
#忽略大小寫
print(sorted(K,key=str.lower))
#反向排序
print(sorted(K,key=str.lower,reverse=True))

輸出結(jié)果:

[-5, -2, 1, 3, 9]
[9, 3, 1, -2, -5]
['Apple', 'Pear', 'banana', 'tomato']
['Apple', 'banana', 'Pear', 'tomato']
['tomato', 'Pear', 'banana', 'Apple']

返回函數(shù)

高階函數(shù)除了可以接受函數(shù)作為參數(shù)外,還可以把函數(shù)作為結(jié)果值返回,我們來聊一聊返回函數(shù)的問題。
當(dāng)我們調(diào)用一個(gè)函數(shù)時(shí),不需要立即得到結(jié)果,想在需要的時(shí)候再進(jìn)行計(jì)算,那么我們就可以返回一個(gè)函數(shù)而不是直接一個(gè)結(jié)果。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def laz_count(*args):

    def count():
        ax = 0
        for n in args:
            ax = ax + n*n
        return ax
    return count

f1 = laz_count(1, 2, 3, 4, 5,6)
f2 = laz_count(1, 2, 3, 4, 5,6)
print(f1())
print(f1==f2)

輸出結(jié)果:

91
False

從上面可以看出,函數(shù)返回值也是一個(gè)函數(shù),需要再次調(diào)用才能得出結(jié)果,并且每次返回的都是一個(gè)新的函數(shù)。

閉包

閉包的定義與java中的內(nèi)部類有些相似,閉包指的是函數(shù)再定義函數(shù)的情況,即:內(nèi)部函數(shù)可以外部函數(shù)的參數(shù)和局部變量,當(dāng)外部函數(shù)返回內(nèi)部函數(shù)時(shí),相關(guān)的參數(shù)和變量都保存在返回的函數(shù)中。這種行為稱之為 “閉包”。

# -*- coding: utf-8 -*-
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())

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

輸出結(jié)果:

9 9 9
1 4 9

由于返回的函數(shù)不是立即執(zhí)行,在調(diào)用執(zhí)行時(shí),i變量已經(jīng)成為3,如果要輸出想要的值,需要再創(chuàng)建一個(gè)函數(shù)將變量i 與函數(shù)綁定。

匿名函數(shù)

匿名函數(shù)其實(shí)就是lambda 表達(dá)式的使用,lambda表達(dá)式的使用場景就是匿名函數(shù),與java 的匿名類很相似。

  • 關(guān)鍵字lambda表示匿名函數(shù),冒號前面的x表示函數(shù)參數(shù)
  • 匿名函數(shù)有個(gè)限制,就是只能有一個(gè)表達(dá)式,表達(dá)式的值就是返回值,不需要return
  • 匿名函數(shù)也可以作為函數(shù)的返回值返回
# -*- coding: utf-8 -*-
L = list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
print(L)

def f(x,y):
    return lambda :x*x+y*y
x = f(2,3)
print(x())

輸出結(jié)果:

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

裝飾器

可以在這代碼運(yùn)行期間動(dòng)態(tài)增加功能的方式,稱之為“裝飾器”(Decorator)。decorator就是一個(gè)返回函數(shù)的高階函數(shù)。裝飾器在java中成為裝飾者模式,需要通過繼承,組合來實(shí)現(xiàn),python中在函數(shù)層面就可以實(shí)現(xiàn)。這就是python 的強(qiáng)大之處

下面我們來通過示例學(xué)習(xí),decorator的用法:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def now(x,y):
    print('2018-1-1',x,y)

now(5,6)
print(now.__name__)

def now(x,y):
    print('2018-1-1',x,y)

f =log(now)
f(1,2)
print(now.__name__)

輸出結(jié)果:

call now():
2018-1-1 5 6
wrapper
call now():
2018-1-1 1 2
now

第一 我們定義了一個(gè)在函數(shù)調(diào)用開始前輸出函數(shù)名的decorator
第二 在使用裝飾器時(shí)可以通過@decorator的方式注解也可以通過傳入函數(shù)的方式
第三 在使用@decorator的方式裝飾器后,函數(shù)的__name__變成了 wrapper 這樣顯然是不合理的,我們目的是為了擴(kuò)展函數(shù)的功能,不是改變函數(shù)的簽名,所以python 中 內(nèi)置了functools.wraps來還原函數(shù)的簽名,具體如下:

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

@log('123')
def now(x,y):
    print('2018-1-1',x,y)

now(5,6)
print(now.__name__)

def now(x,y):
    print('2018-1-1',x,y)

f =log('456')(now)
f(1,2)
print(now.__name__)

輸出結(jié)果:

123 now():
2018-1-1 5 6
now
456 now():
2018-1-1 1 2
now

這個(gè)示例中我們驗(yàn)證了@functools.wraps(func)的用法,并且多層嵌套自定義log輸出的字段。
下面做一個(gè)函數(shù)執(zhí)行時(shí)間的練習(xí):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import functools
import time

def metric(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kw):
        start = time.time()
        result = fn(*args, **kw)
        end = time.time()
        print('%s 執(zhí)行時(shí)間 %.2fs ms' % (fn.__name__, (end - start) * 1000))
        return result
    return wrapper

# 測試
@metric
def fast(x, y):
    time.sleep(0.0012)
    return x + y;

@metric
def slow(x, y, z):
    time.sleep(0.1234)
    return x * y * z;

f = fast(11, 22)
s = slow(11, 22, 33)
if f != 33:
    print('測試失敗!')
elif s != 7986:
    print('測試失敗!')

輸出結(jié)果:

fast 執(zhí)行時(shí)間 2.00s ms
slow 執(zhí)行時(shí)間 124.60s ms

偏函數(shù)

通過傳入函數(shù),和對應(yīng)的規(guī)則,生成一個(gè)新的函數(shù),方便調(diào)用 的方式成為偏函數(shù)partial。

偏函數(shù)的使用:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import functools

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

print(int2('1111'))

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

print(int2('1111'))

輸出結(jié)果:

15
15

創(chuàng)建偏函數(shù)時(shí),實(shí)際上可以接收函數(shù)對象、args和*kw這3個(gè)參數(shù),偏函數(shù)是 functools 模塊中提供的一種固定某些參數(shù)來簡化一些函數(shù)的調(diào)用難度的作用。

參考

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014317848428125ae6aa24068b4c50a7e71501ab275d52000

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評論 2 380