【Python】一篇文章弄懂yield、生成器

1. 迭代器和生成器

  • 迭代器: 可以迭代的對象
  • 生成器: 按一定的算法生成一個序列,比如自然數序列、斐波那契數列等
  • 區別: 實現了迭代器協議(見下文) 的生成器,可以在一定程度上看做迭代器。但迭代器不是生成器。

1.1 range迭代器和生成器

# 這是一個iterator
>>> mylist = [x * x for x in range(3)]
>>> for ele in mylist:
...     print(ele, end=" ")
0 1 4

# 這是一個generator
>>> mylist = (x * x for x in range(3))
>>> for ele in mylist:
...     print(ele, end=" ")
0 1 4

1.2 迭代器

迭代器協議:

  • 實現__iter__()方法,返回一個迭代器
  • 實現__next__()方法,返回當前元素,並指向下一個元素的位置,如果當前位置已無元素,則拋出StopIteration異常。

下面我們使用迭代器協議實現一個斐波那契數列:

# 斐波那契數列
class Fib():
    def __init__(self):
        self.a = 1
        self._b = 1  
    def __iter__(self):   # 不實現__iter__()報錯:'Fib' object is not iterable   Fib不是可迭代的
        return self
    def __next__(self):  # 不實現__next()__報錯:iter() returned non-iterator of type 'Fib'  Fib返回了一個不可迭代對象
        if self.a>100:
            raise StopIteration("終止")
        self.a, self._b = self._b, self.a + self._b
        return self.a

# 直接使用for...in...遍歷
f = Fib()
for i, a in enumerate(f):
    try:
        print('return:', a, '\ta: ',f.a, '\tb:', f._b, '\t__next__():',f.__next__())
    except StopIteration:
        print("==StopIteration==")

# 創建迭代器對象遍歷可迭代對象
it = iter(Fib())
for i in it:
    print(i, end=" ")

# 迭代器對象可以用使用next()遍歷
try:
    next(it)
except StopIteration:
    print("StopIteration")
    
# dis(Fib)  # 查看Python字節碼(類似彙編指令的中間語言)

輸出結果:

return: 1 	a:  1 	b: 2 	__next__(): 2
return: 3 	a:  3 	b: 5 	__next__(): 5
return: 8 	a:  8 	b: 13 	__next__(): 13
return: 21 	a:  21 	b: 34 	__next__(): 34
return: 55 	a:  55 	b: 89 	__next__(): 89
==StopIteration==
1 2 3 5 8 13 21 34 55 89 144 StopIteration

1.3 生成器

  1. generator的功能實際上同iterator完全相同,但其與iterator的區別在於generator中的元素只能被循環一次。這是因爲generator不會在內存中存儲全部的元素,而是動態地產生下一個將被循環的元素。

  2. 使用了yield語句的函數,就叫生成器函數。yield關鍵字返回一個generator,且該generator只能被循環一次

同樣我們實現斐波那契數列:

def fibonacci(n): # 生成器函數 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a  # TODO:每次執行到yield就暫停
        a, b = b, a + b
        counter += 1
f = fibonacci(10) # f 是一個迭代器,由生成器返回生成
 
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        print("StopIteration")
        break

# print(dis(fibonacci))   # 查看Python字節碼(類似彙編指令的中間語言)
print(type(f))  # <class 'generator'> 即類型爲生成器對象
print(dir(f))  # 這個對象實現了__iter__()和__next__()

輸出結果:

0 1 1 2 3 5 8 13 21 34 55 StopIteration
<class 'generator'>
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']

看不懂yield嗎?沒關係,接下來我們以簡單的方式理解生成器函數。

2. 使用yield的生成器函數

yield可以理解爲暫停按鈕,每次執行到yield,保存斷點

# yield可以理解爲暫停按鈕,每次執行到yield,保存斷點
def genarator(value=None):
    yield 1
    yield 2
    print("value: ", end="")
    yield value

g = genarator(3)
print(next(g))   # 執行yield 1,暫停
print(next(g))   # 執行yield 2,暫停
print(next(g))   # 執行yield value,暫停
print(next(g))   # 找不到yield了,raise StopIteration

輸出結果:

1
2
value: 3
Traceback (most recent call last):
  File "E:/Documents/PythonCode/yield.py", line 11, in <module>
    print(next(g))   # 找不到yield了,raise StopIteration
StopIteration

你以爲這就理解完yield了嗎?~ 其實差不多~ 但我們再深入看看吧!

3. 生成器的next()、send()、throw()、close()

直接上代碼!

def echo(value=None):
    print("當next()被調用時,第一次執行開始")
    try:
        while True:
            try:
                value = (yield value) # 第一次next()yield value之後暫停,賦值不會執行。第二次next():賦值yield給value,yield默認爲None
            except Exception as e:
                value = e
    finally:
        print("當close()被調用時,別忘了清理")

# 每一個生成器函數被調用之後,它的函數體並不執行
generator = echo(1)

# 當第一次調用next()時,函數開始執行,執行到yield表達式爲止
# value = (yield value)只是執行了yield value這個表達式
print(next(generator))          # 1

# 當第二次調用next()時,yield表達式的值複製給了value
# 而yield表達式默認“返回值”就是"None",所以此時value的值就是None
print(next(generator))          # None

# yield有一個返回值,send(value)的作用就是控制yield的返回值value
# next(generator)相當於是generator.send(None)
print(generator.send(2))        # 2
print(generator.send(None))     # None

# yield也可以拋出異常
print(generator.throw(TypeError, "Error")) # Error

# 調用close()yield會拋出GeneratorExit異常並且自行處理
generator.close()

# 調用close()之後,對象不再可用,next()或send()會拋出StopIteration異常
next(generator)
# generator.send(None)

輸出結果:

當next()被調用時,第一次執行開始
1
None
2
None
Error
當close()被調用時,別忘了清理
Traceback (most recent call last):
  File "E:/Documents/PythonCode/yield.py", line 35, in <module>
    next(generator)
StopIteration

4 爲什麼說send()相當於next(),我們看看底層代碼!

Python底層是由C語言實現的,讓我們摘出next()和send()來觀察看看!

static PyObject *
gen_iternext(PyGenObject *gen)
{
    return gen_send_ex(gen, NULL, 0);  # 傳入NULL
}


static PyObject *
gen_send(PyGenObject *gen, PyObject *arg)
{
    return gen_send_ex(gen, arg, 0);  # 傳入可變參數
}

從上面的代碼中可以看到,send和next都是調用的同一函數gen_send_ex,區別在於是否帶有參數。

gen_send_ex()參考:https://www.cnblogs.com/coder2012/p/4990834.html

5. 參考博客

Python中的yield關鍵字:https://www.jianshu.com/p/fb67382a0455
Python yield與實現:https://www.cnblogs.com/coder2012/p/4990834.html
菜鳥教程迭代器和生成器:https://www.runoob.com/python3/python3-iterator-generator.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章