Python學習筆記 -- 函數語言程式設計之高階函式
函數語言程式設計
函數語言程式設計(Functional Programming),是一種抽象程度很高的程式設計正規化,純粹的函數語言程式設計語言編寫的函式沒有變數。因此,任意一個函式,只要輸入是確定的,輸出就是確定的。
函數語言程式設計是一種"程式設計正規化"(programming paradigm)。它屬於"結構化程式設計"的一種,主要思想是把運算過程儘量寫成一系列巢狀的函式呼叫。
函數語言程式設計的一個特點就是,允許把函式本身作為引數傳入另一個函式,還允許返回一個函式!Python對函數語言程式設計提供部分支援。由於Python允許使用變數,因此,Python不是純函數語言程式設計語言。
更多內容
高階函式
函數語言程式設計中,可以將函式當作變數一樣使用。接受函式為引數,或者把函式作為結果返回的函式稱為高階函式(Higher-order Functions) 。
def double(x):
return 2 * x
def square(x):
return x * x
def func(g, arr):
return [g(x) for x in arr]
>>> arr1 = func(double, [1, 2, 3, 4])
>>> arr2 = func(square, [1, 2, 3, 4])
>>> arr1
[2, 4, 6, 8]
>>> arr2
[1, 4, 9, 16]
map()/reduce()/filter()/sorted() /apply() 是 Python 中較為常用的高階函式,它們為函數語言程式設計提供了不少便利。
Python 3 中,map
和 filter
還是內建函式,但是由於引入了列表推導和生成器表示式,它們變得沒那麼重要了。列表推導或生成器表示式具有 map
filter
兩個函式的功能,而且更易於閱讀。apply
函式在Python 2.3 中標記為過時,在Python 3 中移除了,因為不再需要它了。如果想使用不定量的引數呼叫函式,可以編寫 fn(*args, **keywords)
,不用再編寫 apply(fn, args,kwargs)
。
map()
map
函式將傳入的函式依次作用到序列的每個元素,並把結果作為新的 Iterator
返回。map
函式語法:
# map(function, iterable, ...)
## 引數 —— function : 函式 ; iterable : 一個或多個序列
## 返回值 —— Python 2.x 返回列表; Python 3.x 返回迭代器(map)
def square(x): # 計算平方數
return x ** 2
>>> map(square, [1,2,3,4,5]) # 計算列表各個元素的平方
<map object at 0x000001B9F81ADEB8>
>>> map(lambda x: x ** 2, [1, 2, 3, 4, 5]) # 使用 lambda 匿名函式
# [1, 4, 9, 16, 25] # python2輸出結果
<map object at 0x000001B9F81ADEB8> # python3輸出結果(可使用list()函式對map函式返回結果進行轉換)
>>> list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))
[1, 4, 9, 16, 25]
# map實現list的值的格式批量轉換
>>> list(map(str, [1, 2, 3, 4]))
['1', '2', '3', '4']
>>> list(map(int, ['1', '2', '3', '4']))
[1, 2, 3, 4]
# 提供了兩個列表,對相同位置的列表資料進行相加
>>> map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
[3, 7, 11, 15, 19]
# -------- 列表推導式實現上述功能 -----------
>>> [square(n) for n in [1,2,3,4,5]]
[1, 4, 9, 16, 25]
>>> [str(n) for n in [1,2,3,4]]
['1', '2', '3', '4']
>>> [int(n) for n in ['1', '2', '3', '4']]
[1, 2, 3, 4]
reduce()
reduce()
函式會對引數序列中元素進行累積。函式將一個數據集合(連結串列,元組等)中的所有資料進行下列操作:用傳給 reduce()
中的函式 function
(有兩個引數)先對集合中的第 1、2 個元素進行操作,得到的結果後繼續和序列的下一個元素做累積計算,直到累積到列表最後一個數據。Python 2 中,reduce 是內建函式,但是在Python 3 中放到functools 模組裡了。reduce()
函式語法:
# reduce(function, iterable[, initializer])
## 引數 —— function:函式,有兩個引數; iterable : 可迭代物件; initializer:可選,初始引數
## 返回值 —— 返回函式計算結果。
# 序列求和實現
from functools import reduce # Python3 需要引入
def add(x, y) : # 兩數相加(或使用 from operator import add)
return x + y
>>> reduce(add, [1,2,3,4,5]) # 計算列表和:1+2+3+4+5
15
>>> reduce(lambda x, y: x * y, [1, 2, 3, 4]) # 相當於 ((1 * 2) * 3) * 4
24
>>> reduce(lambda x, y: x * y, [1, 2, 3, 4], 5) # ((((5 * 1) * 2) * 3)) * 4
120
>>> reduce(lambda x, y: x / y, [2, 3, 4], 72) # (((72 / 2) / 3)) / 4
3
>>> reduce(lambda x, y: x + y, [1, 2, 3, 4], 5) # ((((5 + 1) + 2) + 3)) + 4
15
>>> reduce(lambda x, y: x - y, [8, 5, 1], 20) # ((20 - 8) - 5) - 1
6
>>> f = lambda a, b: a if (a > b) else b # 兩兩比較,取最大值
>>> reduce(f, [5, 8, 1, 10])
10
** 注意:** 在 Python3 中,reduce() 函式已經被從全域性名字空間裡移除了,它現在被放置在 fucntools 模組裡,如果想要使用它,則需要通過引入 functools 模組來呼叫 reduce() 函式:
from functools import reduce
filter()
filter() 函式用於過濾序列,過濾掉不符合條件的元素,Python2返回過濾後的列表,Python3返回迭代器物件(filter),如果要轉換為列表,可以使用 list() 來轉換。
# filter(function, iterable)
## 引數 —— function : 判斷函式; iterable : 可迭代物件
## 返回值 —— 返回一個迭代器物件
# 過濾出列表中的所有奇數
def is_odd(n) :
return n % 2 == 1
>>> list(filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
[1, 3, 5, 7, 9]
# 過濾出1~100中平方根是整數的數
import math
def is_sqr(n) :
return math.sqrt(n) % 1 == 0
>>> list(filter(is_sqr, range(1, 101)))
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
**注意:**Python3 中返回到是一個 filter
類,filter
類實現了 __iter__
和 __next__
方法,可以看成是一個迭代器, 有惰性運算的特性,相對 Python2提升了效能,可以節約記憶體。
sorted()
sorted()
函式對所有可迭代的物件進行排序操作。
sort 與 sorted 區別:
- sort 是應用在 list 上的方法,sorted 可以對所有可迭代的物件進行排序操作。
- list 的 sort 方法返回的是對已經存在的列表進行操作,而內建函式 sorted 方法返回的是一個新的 list,而不是在原來的基礎上進行的操作。
sorted()
語法:
# sorted(iterable, key=None, reverse=False)
## 引數 —— iterable -- 可迭代物件; key -- 主要是用來進行比較的元素,只有一個引數,具體的函式的引數就是取自於可迭代物件中,指定可迭代物件中的一個元素來進行排序; reverse -- 排序規則,reverse = True 降序 , reverse = False 升序(預設)
## 返回值 —— 重新排序的列表
>>>sorted([5, 2, 3, 1, 4])
[1, 2, 3, 4, 5]
# 使用集合的sort()方法,實現類似結果
>>>a = [5,2,3,1,4]
>>> a.sort()
>>> a
[1,2,3,4,5]
# sort()及sorted()的區別在於, sort()返回None,排序操作直接作用在原list上,而sorted()排序後會返回新的list
>>> a = [1,3,2,4];id(a); a.sort(); id(a)
1734959842440
1734959842440
>>> b = [4,3,1,2]; id(b);id(sorted(b));
1734959953864
1734959953672
# sort()和sorted() 另一個區別在於list.sort() 方法只為 list 定義。而 sorted() 函式可以接收任何的 iterable。
>>> sorted({1: 'D', 2: 'B', 3: 'B', 4: 'E', 5: 'A'})
[1, 2, 3, 4, 5]
# 利用key進行倒序排序
>>> example_list = [5, 0, 6, 1, 2, 7, 3, 4]
>>> result_list = sorted(example_list, key=lambda x: x * -1)
>>> print(result_list)
[7, 6, 5, 4, 3, 2, 1, 0]
# 進行反向排序,也可傳入第三個引數 reverse=True:
>>>example_list = [5, 0, 6, 1, 2, 7, 3, 4]
>>> sorted(example_list, reverse=True)
[7, 6, 5, 4, 3, 2, 1, 0]
返回函式
高階函式除了可以接受函式作為引數外,還可以把函式作為結果值返回。
# 返回累加函式變數
def lazy_sum(*args): # 外部函式
def sum(): #內部函式 可以引用外部函式lazy_sum的引數和區域性變數
ax = 0
for n in args:
ax = ax + n
return ax
return sum # 返回函式變數(返回函式sum時,lazy_sum相關引數和變數都儲存在返回的函式中,閉包)
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x00000193F3C89048>
>>> f() # 返回的函式並沒有立刻執行,而是直到呼叫了f()才執行
25
# 呼叫lazy_sum()時,每次呼叫都會返回一個新的函式
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2 # f1()和f2()的呼叫結果互不影響
False
閉包
一個函式返回了一個內部函式,該內部函式引用了外部函式的相關引數和變數,我們把該返回的內部函式稱為閉包(Closure)
- 閉包的最大特點就是引用了自由變數,即使生成閉包的環境已經釋放,閉包仍然存在。
- 閉包在執行時可以有多個例項,即使傳入的引數相同。
# 呼叫lazy_sum()時,每次呼叫都會返回一個新的函式
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1 == f2 # f1()和f2()的呼叫結果互不影響
False
- 利用閉包,還可以模擬類的例項。
# 構造一個類,用於求一個點到另一個點的距離:
from math import sqrt
>>> class Point(object):
def __init__(self, x, y):
self.x, self.y = x, y
def get_distance(self, u, v):
distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
return distance
>>> pt = Point(7, 2) # 建立一個點
>>> pt.get_distance(10, 6) # 求到另一個點的距離
5.0
# 用閉包來實現:
def point(x, y):
def get_distance(u, v):
return sqrt((x - u) ** 2 + (y - v) ** 2)
return get_distance
>>> pt = point(7, 2)
>>> pt(10, 6)
5.0
注意: 儘量避免在閉包中引用迴圈變數,或者後續會發生變化的變數。
def count():
funcs = []
for i in [1, 2, 3]:
def f():
return i
funcs.append(f)
return funcs
>>> f1, f2, f3 = count()
>>> f1()
3
>>> f2()
3
>>> f3()
3
# 原因在於上面的函式 f 引用了變數 i,但函式 f 並非立刻執行,當 for 迴圈結束時,此時變數 i 的值是3,funcs 裡面的函式引用的變數都是 3,最終結果也就全為 3。
# 可以再建立一個函式,並將迴圈變數的值傳給該函式
def count():
funcs = []
for i in [1, 2, 3]:
def g(param):
f = lambda : param # 這裡建立了一個匿名函式
return f
funcs.append(g(i)) # 將迴圈變數的值傳給 g
return funcs
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
2
>>> f3()
3