大家好,我是辰哥
本文對 Python 中的函數式程式設計技術進行了簡單的入門介紹。
頭等函數
在 Python 中,函數是「頭等公民」(first-class)。也就是說,函數與其他資料類型(如 int)處于平等地位。
因而,我們可以将函數指派給變量,也可以将其作為參數傳入其他函數,将它們存儲在其他資料結構(如 dicts)中,并将它們作為其他函數的傳回值。
把函數作為對象
由于其他資料類型(如 string、list 和 int)都是對象,那麼函數也是 Python 中的對象。我們來看示例函數 foo,它将自己的名稱列印出來:
def foo():
print("foo")
由于函數是對象,是以我們可以将函數 foo 指派給任意變量,然後調用該變量。例如,我們可以将函數指派給變量 bar:
bar = foo
bar()
#will print "foo" to the console
語句 bar = foo 将函數 foo 引用的對象指派給變量 bar。
把對象作為函數
當對象可調用時(callable),它們與函數一樣,如 object()。這是通過 __call__ 方法實作的。
示例如下:
class Greeter:
def __init__(self, greeting):
self.greeting = greeting
def __call__(self, name):
return self.greeting + " " + name
每一次配置 Greeter 類的對象時,我們都會建立一個新的對象,即打招呼時可以喊的新名字。如下所示:
morning = Greeter("good morning") #creates the callable object
morning("john") # calling the object
#prints "good morning john" to the console
我們可以調用 morning 對象的原因在于,我們已經在類定義中使用了 __call__ 方法。為了檢查對象是否可調用,我們使用内置函數 callable:
callable(morning) #true
callable(145) #false. int is not callable.
資料結構内的函數
函數和其他對象一樣,可以存儲在資料結構内部。例如,我們可以建立 int to func 的字典。當 int 是待執行步驟的簡寫時,這就會派上用場。
# store in dictionary
mapping = {
0 : foo,
1 : bar
}
x = input() #get integer value from user
mapping[x]() #call the func returned by dictionary access
類似地,函數也可以存儲在多種其他資料結構中。
把函數作為參數和傳回值
函數還可以作為其他函數的參數和傳回值。接受函數作為輸入或傳回函數的函數叫做高階函數,它是函數式程式設計的重要組成部分。
高階函數具備強大的能力。就像《Eloquent JavaScript》中解釋的那樣:
「高階函數允許我們對動作執行抽象,而不隻是抽象數值。」
我們來看一個例子。假設我們想對一個項目清單(list of items)執行疊代,并将其順序列印出來。我們可以輕松建構一個 iterate 函數:
def iterate(list_of_items):
for item in list_of_items:
print(item)
看起來很酷吧,但這隻不過是一級抽象而已。如果我們想在對清單執行疊代時進行列印以外的其他操作要怎麼做呢?
這就是高階函數存在的意義。我們可以建立函數 iterate_custom,待執行疊代的清單和要對每個項應用的函數都是 iterate_custom 函數的輸入:
def iterate_custom(list_of_items, custom_func):
for item in list_of_items:
custom_func(item)
這看起來微不足道,但其實非常強大。
我們已經把抽象的級别提高了一層,使代碼具備更強的可重用性。現在,我們不僅可以在列印清單時調用該函數,還可以對涉及序列疊代的清單執行任意操作。
函數還能被傳回,進而使事情變得更加簡單。就像我們在 dict 中存儲函數一樣,我們還可以将函數作為控制語句,來決定适合的函數。例如:
def add(x, y):
return x + y
def sub(x, y):
return x - y
def mult(x, y):
return x * y
def calculator(opcode):
if opcode == 1:
return add
elif opcode == 2:
return sub
else:
return mult
my_calc = calculator(2) #my calc is a subtractor
my_calc(5, 4) #returns 5 - 4 = 1
my_calc = calculator(9) #my calc is now a multiplier
my_calc(5, 4) #returns 5 x 4 = 20.
嵌套函數
函數還可以在其他函數内部,這就是「内部函數」。内部函數在建立輔助函數時非常有用,輔助函數即作為子子產品來支援主函數的小型可重用函數。
在問題需要特定函數定義(參數類型或順序)時,我們可以使用輔助函數。這種不遵循傳統做法的操作使得解決問題變得更加簡單,示例參見:http://www-inst.eecs.berkeley.edu/~cs61a/sp12/lectures/lect4-2x3.pdf。
假設你想定義一個斐波那契函數 fib(n),該函數隻有一個參數 n,我們必須傳回第 n 個斐波那契數。
定義此類函數的一種可行方式是:使用輔助函數來追蹤斐波那契數列的前兩個項(因為斐波那契數是前兩個數之和)。
def fib(n):
def fib_helper(fk1, fk, k):
if n == k:
return fk
else:
return fib_helper(fk, fk1+fk, k+1)
if n <= 1:
return n
else:
return fib_helper(0, 1, 1)
将該計算從函數主體移到函數參數,這具備非常強大的力量。因為它減少了遞歸方法中可能出現的備援計算。
單表達式函數(Lambda 表達式)
如果我們想在未給函數命名之前寫一個函數要怎麼做?如果我們想寫一個簡短的單行函數(如上述示例中的函數 foo 或 mult)要怎麼做?
我們可以在 Python 中使用 lambda 關鍵字來定義此類函數。示例如下:
mult = lambda x, y: x * y
mult(1, 2) #returns 2
該 mult 函數的行為與使用傳統 def 關鍵字定義函數的行為相同。
注意:lambda 函數必須為單行,且不能包含程式員寫的傳回語句。
事實上,它們通常具備隐式的傳回語句(在上面的示例中,函數想表達 return x * y,不過我們省略了 lambda 函數中的顯式傳回語句)。
lambda 函數更加強大和精準,因為我們還可以建構匿名函數(即沒有名稱的函數):
(lambda x, y: x * y)(9, 10) #returns 90
當我們隻需要一次性使用某函數時,這種方法非常友善。例如,當我們想填充字典時:
import collections
pre_fill = collections.defaultdict(lambda: (0, 0))
#all dictionary keys and values are set to 0
接下來我們來看 Map、Filter 和 Reduce,以更多地了解 lambda。
Map、Filter 和 Reduce
Map
map 函數基于指定過程(函數)将輸入集轉換為另一個集合。這類似于上文提到的 iterate_custom 函數。例如:
def multiply_by_four(x):
return x * 4
scores = [3, 6, 8, 3, 5, 7]
modified_scores = list(map(multiply_by_four, scores))
#modified scores is now [12, 24, 32, 12, 20, 28]
在 Python 3 中,map 函數傳回的 map 對象可被類型轉換為 list,以友善使用。現在,我們無需顯式地定義 multiply_by_four 函數,而是定義 lambda 表達式:
modified_scores = list(map(lambda x: 4 * x, scores))
當我們想對集合内的所有值執行某項操作時,map 函數很有用。
Filter
就像名稱所顯示的那樣,filter 函數可以幫助篩除不想要的項。例如,我們想要去除 scores 中的奇數,那麼我們可以使用 filter:
even_scores = list(filter(lambda x: True if (x % 2 == 0) else False, scores))
#even_scores = [6, 8]
由于提供給 filter 的函數是逐個決定是否接受每一個項的,是以該函數必須傳回 bool 值,且該函數必須是一進制函數(即隻使用一個輸入參數)。
Reduce
reduce 函數用于「總結」或「概述」資料集。例如,如果我們想要計算所有分數的總和,就可以使用 reduce:
sum_scores = reduce((lambda x, y: x + y), scores)
#sum_scores = 32
這要比寫循環語句簡單多了。注意:提供給 reduce 的函數需要兩個參數:一個表示正在接受檢查的項,另一個表示所用運算的累積結果。
- Best Practices for Using Functional Programming in Python:https://kite.com/blog/python/functional-programming/
- Functional Programming Tutorials and Notes:https://www.hackerearth.com/zh/practice/python/functional-programming/functional-programming-1/tutorial/