1. 程式人生 > >Python學習筆記 -- 函數語言程式設計之高階函式

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 中,mapfilter 還是內建函式,但是由於引入了列表推導和生成器表示式,它們變得沒那麼重要了。列表推導或生成器表示式具有 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