Python基礎:函數式編程

1、概述

Python是一門多範式的編程語言,它同時支持過程式、面向對象和函數式的編程範式。所以,在Python中提供了不少符合 函數式編程 風格的特性和工具。html

如下是對 Python中的函數式編程 的簡要總結,關於這一主題更全面的討論能夠參考 Functional Programming HOWTOpython

2、lambda表達式(匿名函數)

除了 Python基礎:函數 中介紹的 def語句,Python還提供了另一種定義函數的方法: lambda表達式express

lambda表達式的語法以下:編程

lambda [arguments]: expression

與def語句相似,lambda表達式建立的函數:markdown

  • 也是可調用對象(接受0個或多個參數,返回一個值)
  • 也是一等公民(first-class)
  • 具備一樣的 參數風格做用域規則
  • 也支持嵌套定義(def中的lambda,或lambda中的lambda)

可是lambda表達式與def語句之間,也存在不少顯著的差別:閉包

差別點 函數(lambda表達式) 函數(def語句)
函數體 只能是單行表達式(expression 能夠是任意複雜的語句(statement
函數返回值 返回值就是函數體中的表達式的求值結果 由函數體中的return語句指定 返回值
函數名 定義後直接返回函數對象(匿名函數 定義後自動爲函數對象綁定函數名
函數定義位置 能夠在任何容許函數對象出現的位置定義(支持即時定義,即時調用) 只能在容許語句出現的位置定義(先定義,後調用)
用途 多用於一次性使用的簡單函數 適用於一切函數和類方法

如下是lambda表達式的簡單示例:app

# def語句
>>> def func(x, y): return x + y # 自動綁定函數名爲func
... 
>>> func
<function func at 0xb76eff7c>
>>> func(1, 2) # 先定義,後調用
3

# lambda表達式
>>> lambda x, y: x + y # 匿名函數(直接返回函數對象)
<function <lambda> at 0xb76ef0d4>
>>> (lambda x, y: x + y)(1, 2) # 即時定義,即時調用
3
>>> f = lambda x, y: x + y # 手動綁定函數名
>>> f(1, 2) # 也能夠先定義,後調用
3
>>> 
>>> ((lambda x: (lambda y: x + y))(1))(2) # 嵌套定義的lambda(較複雜,儘可能避免)
3

3、內建函數filter()、map()、reduce()

一、filter()

函數原型:filter(function, iterable)編程語言

說明:返回一個由iterable中的某些元素組成的列表,這些元素使得function返回True。若iterable爲字符串(或元組),則返回字符串(或元組);不然,老是返回列表。若是function爲None,則默認爲恆等函數(identity function,相似 f(x) = x)。ide

示例:函數式編程

# for循環版本
>>> res = []
>>> for x in 'a1b2c3d4e5f6':
...     if x.isalpha():
...         res.append(x)
... 
>>> res
['a', 'b', 'c', 'd', 'e', 'f']

# filter版本
s = 'a1b2c3d4e5f6'
>>> filter((lambda x: x.isalpha()), s) # iterable爲字符串,則返回字符串
'abcdef'
>>> filter((lambda x: x.isalpha()), tuple(s)) # iterable爲元組,則返回元組
('a', 'b', 'c', 'd', 'e', 'f')
>>> filter((lambda x: x.isalpha()), list(s)) # iterable爲其餘迭代對象,則返回列表
['a', 'b', 'c', 'd', 'e', 'f']
>>> filter(None, list(s)) # function爲None,則默認爲恆等函數
['a', '1', 'b', '2', 'c', '3', 'd', '4', 'e', '5', 'f', '6']

二、map()

函數原型:map(function, iterable, ...)

說明:逐個以iterable中的元素爲參數調用function,並返回結果的列表。若是存在多個iterable,則以最長的爲準(其餘不足的補None),逐個並行取出元素做爲參數調用function(如map(function, iter1, iter2)會返回列表[function(iter1[0], iter2[0]), function(iter1[1], iter2[1]), ...])。若是function爲None,則默認爲恆等函數。

示例:

# for循環版本
>>> res = []
>>> for x in [1, 2, 3, 4, 5]:
...     res.append(x ** 2)
... 
>>> res
[1, 4, 9, 16, 25]

# map版本
>>> map((lambda x: x ** 2), [1, 2, 3, 4, 5])
[1, 4, 9, 16, 25]
>>> map(None, [1, 2, 3, 4, 5]) # function爲None,則默認爲恆等函數
[1, 2, 3, 4, 5]
>>> map((lambda x, y: x + y), [1, 2, 3], [4, 5, 6]) # 存在多個iterable,則返回[1+4, 2+5, 3+6]
[5, 7, 9]
>>> map(None, [1, 2, 3], [4, 5]) # 以最長的iterable爲準,其餘不足的補None
[(1, 4), (2, 5), (3, None)]

三、reduce()

函數原型:reduce(function, iterable[, initializer])

說明:以累加方式逐個取出iterable中的元素做爲參數調用(具備雙參數的)function,從而最終將iterable簡化爲一個值(如reduce(function, [1, 2, 3])會返回function(function(1, 2), 3))。若是存在initializer,則在累加調用中,以它做爲初始的第一個參數。function必須是可調用對象(不能爲None)。

示例:

# for循環版本
>>> total = 0
>>> for x in [1, 2, 3, 4, 5]:
...     total += x
... 
>>> total
15

# reduce版本
>>> reduce((lambda x, y: x + y), [1, 2, 3, 4, 5]) # 至關於((((1+2)+3)+4)+5)
15
>>> reduce((lambda x, y: x + y), [1, 2, 3, 4, 5], 10) # 帶有initializer的reduce,至關於(((((10+1)+2)+3)+4)+5)
25
>>> sum([1, 2, 3, 4, 5], 10) # 等效於上面的reduce
25

4、閉包

閉包(closure)是一個內嵌函數,它可以記住其 外圍做用域 中的全部名字,即便這個做用域 看起來 已經不在外圍。

在如下示例中,內嵌函數action就是一個閉包:

>>> def maker(N):
...     def action(x):
...         return x * N
...     return action
... 
>>> mul10 = maker(10)
>>> mul10(3)
30
>>> mul10(5)
50

儘管函數調用mul10 = maker(10)已經返回並退出了,但後續的mul10卻可以記住整數10,從而計算入參的10倍數。

實際上,外圍做用域(如函數maker對應的代碼範圍)中的全部名字(如參數N)都做爲環境信息被綁定到了action函數上,所以每次調用action時均可以訪問這些環境信息。特別地,能夠經過特殊屬性func_closure來獲取一個函數的自由變量綁定:

>>> def maker(N):
...     def action(x):
...         return x * N
...     print(action.func_closure) # 打印出action函數的func_closure屬性值
...     return action
... 
>>> N = 10
>>> print('int N: id = %#0x, val = %d' % (id(N), N)) # N的值爲10(整數10的地址是0x8e82044)
int N: id = 0x8e82044, val = 10
>>> mul10 = maker(N) # action.func_closure中含有整數10(即自由變量N)
(<cell at 0x90e96bc: int object at 0x8e82044>,)

閉包的這種 可以記住環境狀態 的特性很是有用,Python中有一些其餘特性就是藉助閉包來實現的,好比 裝飾器

5、偏函數應用

一、基本用法

偏函數應用Partial Function Application)是一種簡化函數調用的方式,主要表現爲對函數的部分參數進行固化。

Python中的偏函數應用是藉助 functools.partial 來完成的。例若有一個專用於生成文章標題的函數title:

>>> def title(topic, part):
...     return topic + u':' + part
...

若是要爲 『Python基礎』 系列的多篇文章生成標題,能夠有如下兩種方式:

# 普通版本
>>> print title(u'Python基礎', u'開篇')
Python基礎:開篇
>>> print title(u'Python基礎', u'函數')
Python基礎:函數
>>> print title(u'Python基礎', u'函數式編程')
Python基礎:函數式編程

# 偏函數版本
>>> from functools import partial
>>> pybasic_title = partial(title, u'Python基礎')
>>> print pybasic_title(u'開篇')
Python基礎:開篇
>>> print pybasic_title(u'函數')
Python基礎:函數
>>> print pybasic_title(u'函數式編程')
Python基礎:函數式編程

從上面的示例能夠看出,若是在編碼過程當中遇到了「屢次用相同的參數調用一個函數」的場景,就能夠考慮使用偏函數來固化這些相同的參數,進而簡化函數調用。

二、等效實現

1)默認參數

在上述示例中,若是將函數title的定義改成def title(part, topic=u'Python基礎')也能夠達到相同的效果。可是這種方式的不足之處也很明顯:

  • 須要修改已有函數title的定義
  • 默認參數只能有一個固定值,定義後即不能更改

相比之下,偏函數具備很好的靈活性:既不用修改已有函數的定義,又能夠爲函數的參數固化不一樣的值。

2)lambda表達式

使用 lambda表達式 也能夠實現相似偏函數的功能,而且與默認參數不一樣的是,能夠針對不一樣的參數值定義不一樣的lambda表達式(由於lambda表達式一般是一次性使用的)。例如上述示例中的pybasic_title也能夠實現爲:

>>> pybasic_title = (lambda part: u'Python基礎:' + part)
>>> print pybasic_title(u'開篇')
Python基礎:開篇
>>> print pybasic_title(u'函數')
Python基礎:函數
>>> print pybasic_title(u'函數式編程')
Python基礎:函數式編程

可是,因爲lambda表達式自己的限制(參考 『lambda表達式』 一節),在具備複雜函數的場景中,還得使用偏函數。

3)閉包

最後,使用 閉包 一樣能夠等效地實現偏函數的功能,而且與lambda表達式不一樣的是,它沒有任何限制場景。仍是上面的例子:

>>> def title(topic):
...     def topic_title(part):
...         return topic + u':' + part
...     return topic_title
... 
>>> pybasic_title = title(u'Python基礎')
>>> print pybasic_title(u'開篇')
Python基礎:開篇
>>> print pybasic_title(u'函數')
Python基礎:函數
>>> print pybasic_title(u'函數式編程')
Python基礎:函數式編程

能夠看出,這個閉包版本的惟一缺點是它須要對函數title進行從新定義(與默認參數的狀況有些相似)。

總而言之,若是須要對 已有函數 進行參數固化,偏函數是最佳選擇。

6、列表解析

關於 列表解析(List Comprehensions),在 Python基礎:序列(列表、元組) 中有過簡單介紹。

這裏主要強調兩點:

  • 列表解析能夠用來代替上面提到的一些函數式編程方法
  • 列表解析還有一個生成器版本的近親:生成器表達式

一、用列表解析代替filter()和map()

1)filter()

列表解析能夠徹底代替filter():

  • function不爲None時:[item for item in iterable if function(item)]等價於filter(function, iterable)
  • function等於None時:[item for item in iterable if item]等價於filter(None, iterable)

2)map()

在如下狀況中,列表解析能夠代替map():

  • 只有一個iterable時
    • function不爲None:[function(item) for item in iterable]等價於map(function, iterable)
    • function等於None:[item for item in iterable]等價於map(None, iterable)
  • 多個iterable長度相同時
    • function不爲None:[function(*args) for args in zip(iter1, iter2, ...)]等價於map(function, iter1, iter2, ...)
    • function等於None:zip(iter1, iter2, ...)等價於map(None, iter1, iter2, ...)

若是多個iterable具備不一樣的長度,那麼列表解析就沒法代替map()了。

二、生成器表達式

生成器表達式(Generator Expressions)與列表解析在語法和功能方面都很是類似。兩者的根本差別是:生成器表達式返回一個 生成器,而列表解析返回一個列表。以下所示:

差別點 生成器表達式 列表解析
表示方法 (expr for item in iterable if cond_expr) [expr for item in iterable if cond_expr]
返回值 一個生成器 一個列表

與列表解析相比,生成器表達式具備 延遲計算(lazy evaluation)的特色,所以在使用內存上更有效。關於生成器表達式的實際案例,能夠參考 Python核心編程(第二版) 中的 『8.13』 一節:『生成器表達式』。

相關文章
相關標籤/搜索