本系列主要學(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)用難度的作用。