函數語言程式設計之 Python
上接ofollow,noindex" target="_blank">python 函數語言程式設計學習筆記
參考:www.sigai.cn/
1 函數語言程式設計概述
- 前提:函式在 Python 中是⼀等物件
- 工具:built-in ⾼階函式;lambda 函式;operator 模組;functools 模組
- 模式:閉包與裝飾器
- 替代:⽤用 List Comprehension 可輕鬆替代 map 和 filter(reduce 替代起來⽐比較困難)
- 原則:No Side Effect
何為 No Side Effect? 函式的所有功能就僅僅是返回一個新的值而已,沒有其他行為,尤其是不得修改外部變數。因⽽,各個獨⽴的部分的執⾏順序可以隨意打亂,帶來執⾏順序上的⾃自使得⼀系列新的特性得以實現:⽆鎖的併發;惰性求值;編譯器器級別的效能優化等
1.1 程式的狀態與指令式程式設計
- 程式的狀態首先包含了當前定義的全部變量
- 有了程式的狀態,我們的程式才能不斷往前推進
- 指令式程式設計,就是通過不斷修改變量的值,來儲存當前運⾏的狀態,來步步推進
1.2 函數語言程式設計
- 通過函式來儲存程式的狀態(通過函式建立新的引數和返回值來儲存狀態)
- 函式一層層的疊加起來,每個函式的引數或返回值代表了⼀箇中間狀態
- 指令式程式設計⾥一次變數值的修改,在函數語言程式設計⾥變成了⼀個函式的轉換
- 最自然的方式:遞迴
2 一等函式
一等物件的定義:
- 在運⾏時建立
- 能賦值給變數或資料結構中的元素
- 能作為引數傳給函式
- 能作為函式的返回結果
Python 中,所有函式的都是一等物件,簡稱為一等函式
2.1 高階函式
定義:接受函式為引數,或把函式作為返回結果的函式
2.1.1map
對映
map()
是 Python 內建的高階函式,它接收一個函式f
和一個可迭代物件
,並通過把函式f
依次作用在 可迭代物件 的每個元素上,並返回一個新的可迭代物件。
def f(x): return x * x print('map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])):', list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
show:
map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])): [1, 4, 9, 16, 25, 36, 49, 64, 81]
替代方案:
[x*x for x in range(1,10)]
def format_name(s): s1 = s[0:1].upper() + s[1:].lower() return s1 print("map(format_name, ['adam', 'LISA', 'barT']):", list(map(format_name, ['adam', 'LISA', 'barT'])))
map(format_name, ['adam', 'LISA', 'barT']): ['Adam', 'Lisa', 'Bart']
替代方案:
[format_name(name) for i, name in enumerate(['adam', 'LISA', 'barT'])]
因而,列表推導可以很好的替換 map 函式。
2.1.2filter
過濾器
x = [(), [], {}, None, '', False, 0, True, 1, 2, -3] x_result = filter(bool, x) list(x_result)
[True, 1, 2, -3]
替代方案:
[i for i in x if bool(i)]
print("filter((lambda x: x>0), range(-5, 5)):", list(filter((lambda x: x > 0), range(-5, 5))))
filter((lambda x: x>0), range(-5, 5)): [1, 2, 3, 4]
替代方案:
[x for x in range(-5, 5) if x > 0]
2.1.3reduce
遞推
from functools import reduce m = 2 n = 5 reduce(lambda x, y: x * y, list(range(1, n + 1)), m)
def multiply(a, b): return a * b reduce(multiply, range(1, 5))
2.1.4zip
並行
print("zip( [1, 2, 3], [4, 5, 6]):", list(zip([1, 2, 3], [4, 5, 6])))
show:
zip( [1, 2, 3], [4, 5, 6]): [(1, 4), (2, 5), (3, 6)]
2.1.5sorted
排序
>>> sorted([x * (-1) ** x for x in range(10)])
[-9, -7, -5, -3, -1, 0, 2, 4, 6, 8]
>>> sorted([x * (-1) ** x for x in range(10)], reverse=True)
[8, 6, 4, 2, 0, -1, -3, -5, -7, -9]
>>> sorted([x * (-1) ** x for x in range(10)], key=abs)
[0, -1, 2, -3, 4, -5, 6, -7, 8, -9]
>>> sorted([x * (-1) ** x for x in range(10)], reverse=True, key=abs)
[-9, 8, -7, 6, -5, 4, -3, 2, -1, 0]
min
與max
同理。
2.2partial
functools
這貨用於高階函式:指那些作用於函式或者返回其他函式的函式。通常情況下,只要是可以被當做函式呼叫的物件就是這個模組的目標。
假設有如下函式:
def multiply(x, y): return x * y
現在,我們想返回某個數的雙倍,即:
>>> multiply(3, y=2)
>>> multiply(4, y=2)
>>> multiply(5, y=2)
上面的呼叫有點繁瑣,每次都要傳入y=2
,我們想到可以定義一個新的函式,把y=2
作為預設值,即:
def double(x, y=2): return multiply(x, y)
現在,我們可以這樣呼叫了:
>>> double(3)
>>> double(4)
>>> double(5)
事實上,我們可以不用自己定義double
,利用partial
,我們可以這樣:
from functools import partial double = partial(multiply, y=2)
partial
接收函式multiply
作為引數,固定multiply
的引數y=2
,並返回一個新的函式給double
,這跟我們自己定義double
函式的效果是一樣的。
所以,簡單而言,partial
函式的功能就是:把一個函式的某些引數給固定住,返回一個新的函式。
需要注意的是,我們上面是固定了multiply
的關鍵字引數y=2
,如果直接使用:
double = partial(multiply, 2)
則2
是賦給了multiply
最左邊的引數x
。
from functools import partial def subtraction(x, y): return x - y f = partial(subtraction, 4)# 4 賦給了 x
>>> f(10)# 4 - 10
-6
組合高階函式:
from functools import partial abs_sorted = partial(sorted, key=abs) abs_sorted([x * (-1) ** x for x in range(10)])
show:
[0, -1, 2, -3, 4, -5, 6, -7, 8, -9]
abs_reverse_sorted = partial(sorted, key=abs, reverse=True) abs_reverse_sorted([x * (-1) ** x for x in range(10)])
show:
[-9, 8, -7, 6, -5, 4, -3, 2, -1, 0]
2.3 匿名函式
-
定義:使⽤用
lambda
表示式建立的函式,函式本身沒有名字 -
特點:只能使⽤用純表示式,不能賦值,不能使⽤用
while
和try
等塊語句 -
語法:
lambda [arg1 [,arg2 [,arg3]]]: expression
Expressions get a value; Statements do something
lambda
&def
寫法上:
def lambda
結果上:
def lambda
能用一個表示式直接放到 return 裡返回的函式都可以⽤ lambda 速寫
def multiply(a, b): return a * b multiply_by_lambda = lambda x,y: x * y
List + lambda 可以得到⾏為列表
f_list = [lambda x: x + 1, lambda x: x ** 2, lambda x: x ** 3] [f_list[j](10) for j in range(3)]
[11, 100, 1000]
在 AI 領域里,這種寫法常用於處理資料,比如按預定的⼀系列模式處理資料
下面我們以兩個例子來結束高階函式:
例1:
L = range(6) # 計算l中每個元素的兩倍和平方,並將兩種組成一個列表 # lambda表示式和python函式一樣,也可以接受函式作為引數 def twoTimes(x): return x * 2 def square(x): return x**2 print([list(map(lambda x: x(i), [twoTimes, square])) for i in L]) print(list(filter(lambda x: x % 2 == 0, L))) # 內建reduce函式,計算 L 的和 print(reduce(lambda accumValue, newValue: accumValue + newValue, L, 0))
[[0, 0], [2, 1], [4, 4], [6, 9], [8, 16], [10, 25]] [0, 2, 4] 15
我們依然可以使用列表解析的方式替換map
&filter
:
[[twoTimes(x), square(x)] for x in L] [x for x in L if x % 2 == 0]
通過上面的例子我們發現,使用列表推導要比 map 與 filter 簡潔且易於理解得多。
但是,我們這裡還有一個惰性計算 的坑:
f_list = [lambda x:x**i for i in range(5)] [f_list[j](3) for j in range(5)]
[81, 81, 81, 81, 81]
大家可以思考為什麼會出現這個意想不到的結果?