Python 函數式編程之迭代器、生成器及其應用

python 標準庫中提供了 itertools, functools, operator 三個庫支持函數式編程,對高階函數的支持,python 提供 decorator 語法糖。 迭代器 (iterator)和生成器(generator)概念是 python 函數式編程的基礎,利用迭代器和生成器能夠實現函數式編程中常常用到的 map(), filter(), reduce() 等過程以及 itertools, functools 中提供的絕大部分功能。html

一、迭代器和生成器基礎(next, iter, yield)

迭代器和生成器依賴於 next(), iter() 方法和 yield 表達式java

1.1 next 函數

next(iterator[, default]) 是內置的函數,經過調用 __next__() 方法取得 iterator 的下一個元素,全部元素消耗完再調用就會引發 StopIteration 異常。若是提供了 default 參數,則當取完全部元素後,再調用 next 時會返回 default 值,而不是引發 StopIteration 異常。python

1.2 iter 函數

iter(object[, sentinel]) 內置函數會返回一個迭代器。沒有第2個參數時, object 必須支持迭代協議(__iter__() 方法) 或序列協議 (__getitem__() 方法),不然會引發 TypeError 異常。若是有哨兵 (sentinel)參數, object 必須是可調用的對象,這種方式建立的迭代器每次調用 __next__() 方法時會以無參的形式調用 object ,若是返回值等於哨兵就會引發 StopIteration 異常,不然就返回這個值git

with open('mydata.txt') as fp:
    for line in iter(fp.readline, ''):
        process_line(line)

1.3 yield 表達式

yield 英文意思是生產,是 python 的關鍵字,在函數返回值時用來替換 return 產生一個值。yield 表達式只能用於定義生成器函數中,在函數外使用 yield 會致使 SyntaxError: 'yield' outside function 。express

生成器控制生成器函數的執行,當調用生成器的某個函數時,開始執行,遇到第一個 yield 表達式時,返回 yield 後面表達式的值,而後被掛起(suspend),掛起時保持全部的局部狀態,包括局部變量綁定、指令指針、內部的求值棧、異常處理狀態;當再次調用生成器的某個方法時,執行流會恢復。編程

因此生成器函數很是像協程(coroutine,其它語言中的概念),二者都會 yield 屢次,有多個入口,執行流會被掛起。惟一的區別是生成器函數不能控制 yield 以後,執行流應該從哪繼續,控制老是被轉移到生成器的調用者,因此又被稱爲半協程(semicoroutine)。app

二、迭代器(iterator)

迭代器(iterator)必須至少要定義 __iter__() 和 __next__() 兩個方法,經過 iter() 和 next() 函數調用。 iter() 生成一個迭代器, next() 每調用一次都會返回下一個值,若是已經到最後一個值了,那麼再調用 next() 就會引發 StopIteration 異常。編程語言

#python的迭代器類須要實現__iter__魔法方法返回迭代器實例,還須要實現一個next方法,在迭代到末尾時拋出StopIteration異常表示迭代結束。以下簡單示例:
class SimpleIterator:
    def __init__(self, maxvalue):
        self.current = 0
        self.max = maxvalue

    def next(self):
        result = self.current
        self.current += 1
        if result == self.max:
            raise StopIteration()
        return result

    def __iter__(self):
        return self


li = list(SimpleIterator(5))
print li

################################

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

for 循環實際上就是先將 iter() 做用到容器對象上生成迭代器,而後每次調用 next() ,當引發 StopIteration 時就終止 for 循環。ide

for element in ['a', 'b', 'c']:
    print(element)

# 等價於:
it = iter(['a', 'b', 'c'])
try:
    while True:
        print(next(it))
except StopIteration:
    pass

 

三、生成器(generator)

生成器其實就是一種特殊的迭代器,它使一種更爲高級、更爲優雅的迭代器,使用生成器讓咱們能夠以一種更加簡潔的語法來定義迭代器,它與普通函數相同,只是返回值時用 yield 而不是 return,局部變量和執行狀態在調用之間會自動保存。
讓咱們先明確如下兩點:函數式編程

  • 任意生成器都是迭代器(反過來不成立)
  • 任意生成器,都是一個能夠延遲建立值的工廠
def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

#################################
        
def yield_test(maxvalue):
    i = -1
    while i < maxvalue-1:
        i += 1
        yield i
 
for i in yield_test(10):
    print i

能用 generator(函數) 實現的均可以用 iterator(類)實現,不過生成器 會自動建立 __iter__() 和 __next__() 方法,終止時也會自動引發 StopIteration 異常,於是顯得更緊湊。

利用生成器表達式(generator expression)能夠不用專門定義一個 generator 函數,直接就地使用。生成器表達式與列表表達式(list comprehension)相似,只不過用的是圓括號而不是方括號,因爲生成器只保存上次執行的狀態,因此相比列表表達式,生成器表達式佔用內存更少。

四、流——序列

生成器本質上至關於函數式編程語言中的流的概念,流表面上看是一個序列,但這個序列不是一次構造出來的,而是在須要時構建,函數式編程語言中流是經過惰性求值實現的,能夠看到 python 是經過關鍵詞 yield 實現的。

使用流的概念能夠避免命令式程序設計中賦值帶來的反作用,同時更加簡潔優雅。用序列模擬時間變化,至關因而座標變換,當咱們觀察一個正在移動的粒子時,咱們說該粒子的位置(狀態)正在變化,而從粒子的世界線的觀點看,這裏就根本不涉及任何變化。

TODO : 補充例子

python3 將 python2 中許多列表改爲了迭代器,更加函數式了,例如 range(), zip() 在 python2 中返回列表,而 python3 中返回一個迭代器,因爲迭代器只是在須要(next())時取元素而不是一次就構建整個列表,因此能夠表示很是大的序列甚至無窮序列。

五、生成器——迭代器方法(generator-iterator method)

生成器——迭代器方法 能夠用來控制生成器函數的執行流

  • __next__()

  • send(value) 恢復執行流,並將 value 發送到生成器函數,value 做爲當前 yield 表達式的值

  • throw(type[, value[, traceback]]) 在生成器暫停的地方引發 type 類型的異常,並返回生成器函數產生的下一個值

  • close

>>> def echo(value=None):
...     print("Execution starts when 'next()' is called for the first time.")
...     try:
...         while True:
...             try:
...                 value = (yield value)
...             except Exception as e:
...                 value = e
...     finally:
...         print("Don't forget to clean up when 'close()' is called.")
...
>>> generator = echo(1)
>>> print(next(generator))
Execution starts when 'next()' is called for the first time.
1
>>> print(next(generator))
None
>>> print(generator.send(2))
2
>>> generator.throw(TypeError, "spam")
TypeError('spam',)
>>> generator.close()
Don't forget to clean up when 'close()' is called.

六、應用

有了上面迭代器和生成器,就能夠實現各類函數式編程了,下面是函數式編程中經常使用的幾個函數,更多例子能夠查看 itertools 文檔

6.1 map 函數

當 map(function, iterable,...) 接收 n 個 iterable 時,每次在各 iterable 中各取一個元素傳給 function 做參數,因此 function 必須可以接收 n 個參數,當各個 iterable 長度不同時按最短的終止,例如 map(lambda x,y: x+y, [1,2], [3,4], [5,6]) 會報錯, map(lambda x,y: x+y, 'abcd', 'def') 返回的迭代器依次爲 'ad', 'be', 'cf'

# 這個實現很差,用到了 zip,不過 zip 也能夠經過生成器實現(見後面)
def map(function, *iterables):
    for args in zip(*iterables):
        yield function(*args)

itertools.starmap(function, iterable) 只接收一個 iterable,當 function 接收多個參數時,各個參數是放在元組中的,例如 itertools.starmap(pow, [(2,5), (3,2), (10,3)]) 返回迭代器的值依次爲 32, 9, 1000。

def starmap(function, iterable):
    for args in iterable:
        yield function(*args)

6.2 filter 函數

filter(function, iterable) 函數至關於生成器表達式 (item for item in iterable if function(item)) ,沒有提供 function 參數時至關於 (item for item in iterable if item)

itertools 中提供 filterfalse(predicate, iterable) 函數, filterfalse(lambda x: x%2, range(10)) 獲得 0,2,4,6,8, 的迭代器

def filterfalse(predicate, iterable):
    if predicate i None:
        predicate = bool
    for x in iterable:
        if not predicate(x):
            yield x

6.3 reduce 函數

reduce(function, iterable[, initializer]) 函數將 function 從左到右兩個兩個地累計做用到 iterable 上,從而將 iterable 歸約到一個值,例如 reduce(lambda x, y: x+y, [1,2,3,4]) 會計算 (((1+2)+3)+4),從而獲得10。 python3 已經將內置的 reduce 函數移到 functools 模塊中了

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

6.4 枚舉函數(enumerate)

enumerate(iterable, start=0) 生成一個枚舉迭代器,每次調用 next() 時會返回一個元組,包含計數(從 start 開始)和值(iterable)

seasons = ['Spring', 'Summer', 'Fall', 'Winter']
list(enumerate(seasons)) # => [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
list(enumerate(seasons, start=1)) # => [(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

能夠利用生成器實現 enumerate

def enumerate(sequence, start=0):
    n = start
    for elem in sequence:
        yield n, elem
        n += 1

6.5 zip 函數

zip(*iterables) 返回元組迭代器,iterables 長度不一樣時,按最短的截斷, itertools 模塊中有 zip_longest() 函數。

a = [1, 2, 3]
b = [1, 4, 9]
c = [1, 8, 27]
list(zip(a, b, c))
# => [(1,1,1), (2,4,8), (3,9,27)]

利用生成器實現 zip

def zip(*iterables):
    sentinel = object()
    iterators = [iter(it) for it in iterables]
    while iterators:
        result = []
        for it in iterators:
            elem = next(it, sentinel)
            if elem in sentinel:
                return
            result.append(elem)
        yield tuple(result)

6.6 累積器(accumulate)

標準庫 itertools 提供 accumulate(iterable[,func]) 函數,將 func 函數做用到 iterable 相鄰元素上,累計起來,返回的也是一個迭代器。例如 accumulate([1,2,3,4,5]) 返回迭代器,其值依次爲 1, 3, 6, 10, 15,而 accumulate([1, 2, 3, 4, 5], operator.mul) 則返回迭代器的值依次爲 1, 2, 6, 24, 120

一樣也能夠用生成器實現 accumulate

def accumulate(iterable, func=operator.add):
    it = iter(iterable)
    total = next(it)
    yield total
    for element in it:
        total = func(total, element)
        yield total

6.7 循環函數(cycle)

itertools.cycle(iterable) 將 iterable 串起來做爲 iterator 返回,是無窮循環。例如 cycle('ABCD') 返回迭代器,其值是 A B C D A B C D A …

利用生成器實現 cycle

def cycle(iterable):
    saved = []
    for element in iterable:
        yield element
        saved.append(element)
    while saved:
        for element in saved:
            yield element

6.8 groupby 函數

利用迭代器實現 groupby

class groupby:
    # [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
    # [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
    def __init__(self, iterable, key=None):
        if key is None:
            key = lambda x: x
        self.keyfunc = key
        self.it = iter(iterable)
        self.tgtkey = self.currkey = self.currvalue = object()
    def __iter__(self):
        return self
    def __next__(self):
        while self.currkey == self.tgtkey:
            self.currvalue = next(self.it)    # Exit on StopIteration
            self.currkey = self.keyfunc(self.currvalue)
        self.tgtkey = self.currkey
        return (self.currkey, self._grouper(self.tgtkey))
    def _grouper(self, tgtkey):
        while self.currkey == tgtkey:
            yield self.currvalue
            self.currvalue = next(self.it)    # Exit on StopIteration
            self.currkey = self.keyfunc(self.currvalue)

 

七、Refer:

[1] Python 函數式編程

http://dengshuan.me/techs/python-functional.html

[2] Java FP: Java中函數式編程的謂詞函數(Predicates)第一部分

http://ifeve.com/functional-style-in-java-with/

[3] itertools — 建立高效迭代器的函數

http://python.usyiyi.cn/python_278/library/itertools.html

[4] itertools — Functions creating iterators for efficient looping (高效循環迭代器建立函數)

http://data.digitser.net/python_3.4.2/zh-CN/library/itertools.html

[5] PYTHON-進階-ITERTOOLS模塊小結

http://wklken.me/posts/2013/08/20/python-extra-itertools.html

[6] (譯)Python關鍵字yield的解釋(stackoverflow)

http://pyzh.readthedocs.org/en/latest/the-python-yield-keyword-explained.html

[7] Python yield 使用淺析

http://my.oschina.net/leejun2005/blog/94175

[8] Python函數式編程指南(三):迭代器

http://www.cnblogs.com/huxi/archive/2011/07/01/2095931.html

[9] Python函數式編程指南(四):生成器

http://www.cnblogs.com/huxi/archive/2011/07/14/2106863.html

[10] 可迭代對象 vs 迭代器 vs 生成器

http://python.jobbole.com/86258/

相關文章
相關標籤/搜索