廖雪峯Python教程學習筆記(4)

7. 函數式編程

函數式編程,即 Functional Programming,有以下幾個特點:

  • 沒有副作用:對於任意一個函數,只要輸入是確定的,輸出就是確定的,這種純函數沒有副作用;
  • 接近自然語言,易於理解:函數式編程的自由度很高,可以寫出很接近自然語言的代碼;
  • 函數是“第一等公民”:函數與其他數據類型一樣,處於平等地位,可以賦值給其他變量,也可以作爲參數,傳入另一個函數,或者作爲別的函數的返回值。

Python 對函數式編程提供部分支持。由於Python允許使用變量,因此,Python不是純函數式編程語言。

7.1 高階函數

高階函數,即 Higher-order function,有幾個特點:

變量可以指向函數

這裏以 abs() 函數來作說明:

print(abs(-10)) # 10
# 直接打印 abs 呢?
print(abs) # <built-in function abs>
# 把函數本身賦值給變量
f = abs
print(f) # <built-in function abs>
# 使用變量 f 來調用 abs() 函數
print(f(-100)) # 100

可以看到,可以用變量指向函數,並且可以通過這個變量來調用函數。

函數名也是變量

函數名其實就是指向函數的變量。
通過 abs() 這個內置函數進行說明,這個函數的作用是計算絕對值。這裏我們完全可以把 abs 這個函數名看作變量,它指向了一個可以計算絕對值的函數。既然 abs 是一個變量,當然可以指向其他對象。

# 函數名也是變量
# abs = 10
# print(abs(-10)) # 報錯
'''
Traceback (most recent call last):
  File "higher_order_function_intro.py", line 13, in <module>
    print(abs(-10))
TypeError: 'int' object is not callable
'''

上面我們把 abs 這個函數名,當做變量,指向了 10。接着,又期望 abs(-10) 可以得出結果 10。實際上,是報錯的。其實現在 abs 是指向了 10,而不再指向絕對值函數了。這一點是需要注意的。

傳入函數

既然變量可以指向函數,而函數的參數可以接收變量,那麼一個函數就可以接收另一個函數作爲參數,這種函數就是高階函數

# -----傳入函數------
def add(x, y, f):
    return f(x) + f(y)

print(add(-5, 6, abs)) # 11

7.1.1 map/reduce

map 函數接收多個參數,第一個是函數,後面的參數是一個或多個Iterable,用逗號隔開,返回一個 Iteratormap 函數語法:

map(function, iterable, …)

map 函數將傳入的函數依次作用到Iterable 中的每個元素上,把結果作爲 Iterator 返回。map 函數是 Python 中的內置函數。

from collections.abc import Iterator, Iterable
#  map() 函數
# 把 f(x) = x ^ 2, 作用在一個 list [1,2,3,4,5,6,7,8,9] 上
def f(x):
    return x ** 2

r = map(f, [1,2,3,4,5,6,7,8,9])
print(isinstance(r, Iterator)) # True,說明 r 是 Iterator。
print(isinstance(r, Iterable)) # True,說明 r 是 Iterable。
# Iterator 是一個惰性序列
# 迭代方式一:
print(list(r))
# 迭代方式二:
for y in r:
    print(y)

利用 map 函數,把 list 中的每個整數轉爲字符串:

print(list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# 打印結果:['1', '2', '3', '4', '5', '6', '7', '8', '9']

利用 map 函數,對兩個列表相同位置的元素進行相加

print(list(map(lambda x, y : x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10]))) # [3, 7, 11, 15, 19]

reduce 函數:第一個參數是函數,第二個參數是 Iterable,第三個參數是可選參數,reduce 函數的返回值與作爲第一個參數的函數的返回值是一致的。需要注意的是,作爲第一個參數的函數必須接收兩個參數。reduce 函數的語法:

reduce(function, sequence [, initial] )

reduce 函數的作用是如果不提供 initial 參數,則先用函數對可迭代序列中的第一個元素和第二個元素進行操作,得到一個結果,再用函數對上一步得到的結果和第三個元素進行操作,以此類推,直到達到最後的結果爲止。如果提供 initial 參數,則先用函數對 initial 的值和可迭代序列中的第一個元素進行操作。

reduce 函數並不是 Python 的內置函數,使用 reduce 函數需要先進行導入:

from functools import reduce

計算列表的和:

def add(x, y):
    return x + y
print(reduce(add, [1, 2, 3, 4, 5])) # 15

把序列[1,3,5,7,9]變爲整數13579:

def trans(x, y):
    return 10 * x + y
print(reduce(trans, [1, 3, 5, 7, 9])) # 13579

str類型 轉 int類型:

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
          '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2int(s):
    def char2num(ch):
        return DIGITS[ch]
    def fn(x, y):
        return 10 * x + y
    return reduce(fn, map(char2num, s))
print('1378') # 1378

上面的例子涉及了局部函數:Python 支持在函數體內定義函數,這既是局部函數。

7.1.2 filter

filter 函數接收兩個參數,第一個參數是函數,第二個參數是可迭代對象,返回值是迭代器對象。

filter 函數是 Python 的內置函數,filter 函數的語法爲:

filter(function, iterable)

filter 函數的作用是過濾序列,將序列中的每個元素作爲參數傳遞給函數,函數判斷結果是 TrueFalse,爲 True 時,則該元素保留在新的列表中,爲False 時,則該元素不保留在新的列表中。

需要注意的是,當 function 參數不是 None 時,filter(function,iterable) 和生成器表達式 (item for item in iterable if function(item)) 是等價的;當 function 參數是None 時,等價於 (item for item in iterable if item)

在一個 list 中,刪掉偶數,只保留奇數:

# 判斷是不是奇數的方法
def is_odd(n):
    return n & 1 != 0
print(list(filter(is_odd, [1, 2, 3, 5, 8, 9, 12]))) # [1, 3, 5, 9]

在一個 list 中,刪除負數,只保留整數:

print(list(filter(lambda x : x > 0, [-1, -2, 3, 4, 5]))) # [3, 4, 5]

第一個參數爲 None時,保留判斷爲 True的元素:

print(list(filter(None, [0, 1, '', False, True]))) # [1, True]

7.1.3 sorted

sorted 函數的語法:

sorted(iterable, *, key=None, reverse=False)

第一個參數是一個可迭代對象,
第二個參數是個函數,用來處理進行比較的元素,只有一個參數,具體的函數的參數就是取自於可迭代對象中,指定可迭代對象中的一個元素來進行排序。
第三個參數是排序規則,reverse = True 降序 , reverse = False 升序(默認)。
返回一個新的 list。

參數列表裏的 *表示命名關鍵字參數,意思就是後面兩個參數的名字 keyvalue 是被限定的,而且如果傳遞實參時,必須要帶上參數名。

我們還注意到,keyvalue 這兩個參數使用了默認參數。

# 對 list 進行從小到大排序
print(sorted([36, -3, 1, 20, -5]))  # [-5, -3, 1, 20, 36]
# 對 list 進行從大到小排序
print(sorted([36, -3, 1, 20, -5], reverse=True))  # [36, 20, 1, -3, -5]
# 對 list 按絕對值從小到大排序
print(sorted([36, -3, 1, 20, -5], key=abs))  # [1, -3, -5, 20, 36]

7.2 返回函數

函數作爲返回值

以求和函數爲例說明:

# 函數作爲返回值,不會立即求和
def lazy_sum(*args):
	# 這裏定義了局部函數
    def sum():
        result = 0
        for n in args:
            result += n
        return result
    return sum

f = lazy_sum(1, 2, 3, 4)
print(f) # <function lazy_sum.<locals>.sum at 0x7f51b0fd28c8>
print(f()) # 10

上面的代碼使用了閉包,那麼什麼是閉包呢?

一個函數定義中引用了函數外定義的變量,並且該函數可以在其定義環境外被執行。這樣的一個函數我們稱之爲閉包

因爲在上面的代碼中,函數 sum 就是一個閉包,它引用了 lazy_sum 的參數 args

每次調用 lazy_sum,都會返回一個新的函數,即便是傳入的參數是一模一樣的:

# 每次調用 lazy_sum ,都會返回一個新的函數變量
f1 = lazy_sum(1, 2, 3, 4) 
f2 = lazy_sum(1, 2, 3, 4)
print(f1) # <function lazy_sum.<locals>.sum at 0x7f8c919ea950>
print(f2) # <function lazy_sum.<locals>.sum at 0x7f8c919ea9d8>

但是, 返回閉包時牢記一點:返回函數不要引用任何循環變量,或者後續會發生變化的變量。

7.3 匿名函數

以之前學習 map 函數時,把容器中的數字,經過 f(x) = x^2,變換每一個元素,得到新的容器的例子來說明:

之前的寫法是:

def f(x):
    return x ** 2
print(list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# 打印結果:[1, 4, 9, 16, 25, 36, 49, 64, 81]

匿名函數的寫法是:

print(list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# 打印結果:[1, 4, 9, 16, 25, 36, 49, 64, 81]

可以看到我們使用 lambda x: x * x 替代了函數 f

關鍵字 lambda表示匿名函數,冒號前面的 x 表示函數參數,和函數一樣,參數列表可以包含多個參數,也可以不包括任何參數;冒號後面的 x * x 表示函數體,但是匿名函數的函數體只能有一個表達式,並且不用寫return,返回值就是這個表達式的結果。

匿名函數也是函數,而函數可以賦值給變量,所以匿名函數也可以賦值給變量。

g = lambda x, y: x + y
print(g(1, 1)) # 2

匿名函數也可以作爲返回值返回。

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

print(add(2, 3)()) # 5

注意上面的例子中,匿名函數的參數列表爲空,但是冒號可以不能省略的。

7.4 裝飾器

裝飾器這一部分有點不好懂。
Python 中的裝飾器(decorator)允許在不修改原函數定義的情況下,在代碼允許期間動態添加功能。這樣的好處是,抽離出大量與函數功能本身無關的代碼到裝飾器中並獲得重用。

參考:理解 Python 裝飾器看這一篇就夠了

7.5 偏函數

偏函數(partial function),是由 Python 的 functools 模塊提供的功能。

偏函數的作用是:當函數的參數個數太多,需要簡化調用時,可以使用 functools.partial 創建一個新的函數,新函數可以固定住原函數的部分參數,從而在調用時更簡單。

int 函數把字符串轉爲整數的例子來說明:

大量需要一個把字符串,按二進制轉爲十進制的整數,可以這樣寫:

print(int('100000', base = 2)) # 32,把 100000 按照二進制,轉爲十進制是32
print(int('10000', base = 2)) # 16,把 10000 按照二進制,轉爲十進制是16
print(int('1000', base = 2)) # 8,把 1000 按照二進制,轉爲十進制是8

抽取一個函數 int2,是這樣的:

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

print(int2('100000')) # 32
print(int2('10000')) # 16
print(int2('1000')) # 8

使用偏函數,需要先導入 import functools

int2 = functools.partial(int, base = 2)
print(int2('100000')) # 32
print(int2('10000')) # 16
print(int2('1000')) # 8
print(int2('100000',  base = 10)) # 100000
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章