1. 程式人生 > >《Python高階程式設計》(三)生成器

《Python高階程式設計》(三)生成器

生成器

定義

生成器是一個函式,它並不執行並返回一個單一值,而是按照順序返回一個或多個值

生成器的語法

生成器函式的特徵就是在函式內部有一個或多個yield語句。
Python 2中,yield和return不能共存;python 3中可同時存在。
yield語句:命令函式返回一個值給呼叫者,但不會終止函式的執行。執行會暫時停頓直到呼叫程式碼重新恢復生成器,在停止的地方再次開始執行。

生成器案例

import time
def fib():
    numbers = []
    while True:
        if len(numbers) < 2:
            numbers.append(1)
        else:
            numbers.append(sum(numbers))
            numbers.pop(0)
            yield numbers[-1]

f = fib()
for i in f:
    print i
    time.sleep(1)
  • 注意:f = fib()語句執行後,函式程式碼並沒有執行,直譯器唯一完成的就是識別生成器的出現並返回一個generator物件,該物件在每執行一次程式碼時就請求一個值。
  • 可以使用內建的next函式請求第一個值
  • 函式只是儲存了最新的兩個數字,沒有把龐大的數列儲存在記憶體
  • TopIterator異常:迭代生成器丟擲StopIteration時,標誌著生成器迭代完成並已退出
  • python3中消除了yield和return不能在一個函式中共存的限制。但return等同於raise StopIteration
  • yield語句實際上是一個表示式,有返回值

生成器之間的互動

一個求平凡的生成器

def squares():
    cursor = 1
    while True:
        yield cursor ** 2
        cursor += 1

sq = squares()
print sq.next()  # 1
print sq.next()  # 4

該生成器是單向的,send方法允許生成器反向溝通

def squares1(cursor=1):
    while True:
        response = yield cursor ** 2
        if response:
            cursor = int(response)
        else:
            cursor += 1

sq = squares1()
print sq.next()  # 1
print sq.next()  # 4
print sq.send(7) # 49;值7傳送給生成器,賦給cursor變數
print sq.next()  # 64
  • send方法:允許生成器反向溝通,因為yield語句實際上就是一個表示式
  • send的目的是提供一個與生成器雙向互動的機制,確定是否(如何)處理髮送給生成器的值是生成器的責任

迭代物件和迭代器

  • 迭代器 是指包含__next__方法的任何物件。生成器是一種迭代器。range是迭代器,但不是生成器
  • 迭代物件 是指任何定義了__iter__方法的物件。可迭代物件的__iter__方法負責返回一個迭代器
print next(range(5)) #是迭代器,但不是生成器
TypeError: list object is not an iterator
print range(5).__iter__()
<listiterator object at 0x01662BB0>

標準庫中的生成器

  • range(前一節)
  • dict.items及其家族
    • python 2中:iterkeys,itervalues,iteritems
    • python 3中:keys,values,items
    • 如果在迭代期間試圖修改字典,則會報RuntimeError。因為items迭代器是一個僅從引用的字典中讀取資料的生成器,如果執行時字典表發生了變化,它將不知道自己應該做什麼,因此丟擲異常。
    • 案例
	dictionary = {'foo':'bar', 'baz':'bacon'}
	iterator = iter(dictionary.items())
	print next(iterator)
	print next(iterator)

執行結果:
(‘foo’, ‘bar’)
(‘baz’, ‘bacon’)

  • zip:使用zip的目的是在不同的結構中輸出其迭代物件的返回成員,一次輸出一個集合。緩解了對記憶體的需求。
>>> z = zip(['a','b','c','d'], ['x','y','z'])
>>> next(z)
('a', 'x')
>>> next(z)
('b', 'y')
>>> next(z)
('c', 'z')
>>> next(z)
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    next(z)
StopIteration
  • map:該函式將一個能接受N個引數和N個迭代物件的函式作為引數,並且計算每個迭代物件的序列成員的函式結果
    函式定義:map()是 Python 內建的高階函式,它接收一個函式 f 和一個 list,並通過把函式 f 依次作用在 list 的每個元素上,得到一個新的 list 並返回。
>>> m = map(lambda x,y: max([x,y]), [4,1,7], [3,4,5]) 
>>> m
<map object at 0x0208CB50>
>>> next(m)
4
>>> next(m)
4
>>> next(m)
7
>>> next(m)
Traceback (most recent call last):
  File "<pyshell#15>", line 1, in <module>
    next(m)
StopIteration
  • 檔案物件:readline方法可以一次讀取一行
>>> f = open('aa.txt') #open函式返回的結果物件除了其他身份外,還是個生成器
>>> next(f)
'one line\n'
>>> next(f)
'two line\n'
>>> next(f)
Traceback (most recent call last):
  File "<pyshell#23>", line 1, in <module>
    next(f)
StopIteration

何時編寫生成器

  • 原則:只有當需要值時才會確定這個值,而不是提前準備好;即使需要所有資料,但是如果不需要一次性處理所有資料,仍然可以僅儲存需要的資料。
  • 兩個理由:
    • 分塊訪問資料:需要涵蓋必須分塊訪問資料的情況,但是這種情況沒必要儲存整個副本(例如readline和dict.items)
    • 分塊計算資料:僅在需要它時計算資料(例如range和fibonacci函式)

生成器單例模式

很多生成器是單例模式;簡單的生成器函式不是單例模式

class Fabonacci(object):
    def __init__(self):
        self.numbers = []
    def __iter__(self):
        return self
    def __next__(self):
        if len(self.numbers) < 2:
            self.numbers.append(1)
        else:
            self.numbers.append(sum(self.numbers))
            self.numbers.pop(0)
        return self.numbers[-1]
    def send(self, value):
        pass
    # For python2 compatibility
    next = __next__

f = Fabonacci()
i1 = iter(f)
print next(i1)
print next(i1)
i2 = iter(f)
print next(i2)

執行結果:
1
1
2

  • 這是一個Fabonacci類,實現了生成器的協議,它也是一個迭代物件,並且將自己作為引數響應iter,即每個Fabonacci物件只有一個迭代器:它自己
  • 注意:弄明白一個迭代物件是否允許有多個迭代器:有些迭代物件可以有多個迭代器,有些迭代物件則不可以

生成器內部的生成器

  • 生成器委託:yield from 為生成器提供一種呼叫其他生成器的直接方式。
  • 案例:python 3.3之前,將子生成器組合成一個生成器的方法是顯式地迭代它們,即:full_gen
>>> def gen1():
    yield 'foo'
    yield 'bar'
>>> def gen2():
    yield 'spam'
    yield 'eggs'
>>> def full_gen():
    for word in gen1():
        yield word
    for word in gen2():
        yield word
>>> def fuu_gen2():  #僅python3.3以上版本支援
	yield from gen1()  #生成器委託
	yield from gen2()
>>> f =full_gen()
>>> for i in f:
	print i
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(i)?
>>> for i in f:
	print (i)
foo
bar
spam
eggs
>>> f = fuu_gen2()
>>> for i in f:
	print (i)
foo
bar
spam
eggs