函數進階-07生成器

一丶yield關鍵字

yield的英文單詞意思是生產,在函數中但凡出現yield關鍵字,再調用函數,就不會繼續執行函數體代碼,而是會返回一個值。python

def func():
    print(1)
    yield
    print(2)
    yield

g = func()
print(g)

<generator object func at 0x10ddb6b48>
生成器的本質就是迭代器,同時也並不只僅是迭代器,不過迭代器以外的用途實在是很少,因此咱們能夠大聲地說:生成器提供了很是方便的自定義迭代器的途徑。而且從Python 2.5+開始,[PEP 342:經過加強生成器實現協同程序]的實現爲生成器加入了更多的特性,這意味着生成器還能夠完成更多的工做。這部分咱們會在稍後的部分介紹。編程

def func():
    print('from func 1')
    yield 'a'
    print('from func 2')
    yield 'b'

g = func()
print(F"g.__iter__ == g: {g.__iter__() == g}")

res1 = g.__next__()
print(f"res1: {res1}")

res2 = next(g)
print(f"res2: {res2}")

# next(g)  # StopIteration

g.__iter__ == g: True from func 1 res1: a from func 2 res2: b多線程

def func():
    print('from func 1')
    yield 'a'
    print('from func 2')
    yield 'b'


g = func()
for i in g:
    print(i)

print(f"list(func()): {list(func())}")

from func 1 a from func 2 b from func 1 from func 2 list(func()): ['a', 'b']併發

1.1 yield和return二選一

既然生成器函數也是函數,那麼它可使用return輸出返回值嗎?函數

親,既然你都選擇自定義一個函數做爲生成器,你還return幹啥?若是這是在Python2中,Python解釋器會贈送給你一個異常,可是在Python3中,他也無論你這種傻瓜行爲了。學習

def i_wanna_return():
    yield 'a'
    yield 'b'
    return None
    yield 'c'

for i in i_wanna_return():
    print(i)

a b線程

1.2 迭代器套迭代器

若是我須要在生成器的迭代過程當中接入另外一個生成器的迭代怎麼辦?寫成下面這樣好傻好天真。而且你這樣作的意圖是什麼???指針

def sub_generator():
    yield 1
    yield 2
    for i in range(3):
        yield i

for i in sub_generator():
    print(i)

1 2 0 1 2code

def sub_generator():
    yield 1
    yield 2
    yield from range(3)

for i in sub_generator():
    print(i)

1 2 0 1 2協程


二丶協同程序

協同程序(協程)通常來講是指這樣的函數:

 * 彼此間有不一樣的局部變量、指令指針,但仍共享全局變量;
 * 能夠方便地掛起、恢復,而且有多個入口點和出口點;
 * 多個協同程序間表現爲協做運行,如A的運行過程當中須要B的結果才能繼續執行。
協程的特色決定了同一時刻只能有一個協同程序正在運行(忽略多線程的狀況)。得益於此,協程間能夠直接傳遞對象而不須要考慮資源鎖、或是直接喚醒其餘協程而不須要主動休眠,就像是內置了鎖的線程。在符合協程特色的應用場景,使用協程無疑比使用線程要更方便。

從另外一方面說,協程沒法併發其實也將它的應用場景限制在了一個很狹窄的範圍,這個特色使得協程更多的被拿來與常規函數進行比較,而不是與線程。固然,線程比協程複雜許多,功能也更強大,因此我建議你們緊緊地掌握線程便可,是否是聽了一臉懵逼,那麼就別管他了,由於併發編程你會從新學習他。所以這一節裏我也就不列舉關於協程的例子了,如下介紹的方法瞭解便可。

因爲Python2.5+對生成器的加強實現了協程的其餘特色,在這個版本中,生成器加入了以下方法:

2.1 send(value)

send是除next外另外一個恢復生成器的方法。Python2.5+中,yield語句變成了yield表達式,這意味着yield如今能夠有一個值,而這個值就是在生成器的send方法被調用從而恢復執行時,調用send方法的參數。

def h():
    print('--start--')
    first = yield 5  # 等待接收 Fighting! 值
    print('1', first)
    second = yield 12  # 等待接收 hahaha! 值
    print('2', second)
    yield 13
    print('--end--')


g = h()
first = next(g)  # m 獲取了yield 5 的參數值 5
# (yield 5)表達式被賦予了'Fighting!',  d 獲取了yield 12 的參數值12
second = g.send('Fighting!')
third = g.send('hahaha!')  # (yield 12)表達式被賦予了'hahaha!'
print(f'--over--')
print(f"first:{first}, second:{second}, third:{third}")

--start-- 1 Fighting! 2 hahaha! --over-- first:5, second:12, third:13

  • 調用send傳入非None值前,生成器必須處於掛起狀態,不然將拋出異常。不過,未啓動的生成器仍可使用None做爲參數調用send。
  • 若是使用next恢復生成器,yield表達式的值將是None。

    2.2 close()

    這個方法用於關閉生成器。對關閉的生成器後再次調用next或send將拋出StopIteration異常。
def repeater():
    n = 0
    while True:
        n = (yield n)

r = repeater()
r.close()
print(next(r))  # StopIteration

2.3 throw(type,value=None,traceback=None)

中斷Generator是一個很是靈活的技巧,能夠經過throw拋出一個GeneratorExit異常來終止Generator。Close()方法做用是同樣的,其實內部它是調用了throw(GeneratorExit)的。咱們看close的源代碼:

def close(self):
    try:
        self.throw(GeneratorExit)
    except (GeneratorExit, StopIteration):
        pass 
    else:
        raise RuntimeError("generator ignored GeneratorExit") # Other exceptions are not caught


三丶自定義range()方法

def my_range(start, stop, step=1):
    while start < stop:
        yield start
        start += 1

g = my_range(0, 3)
print(f"list(g): {list(g)}")

list(g): [0, 1, 2]

四丶總結

yield:
 1.提供一種自定義迭代器的方式
 2.yield能夠暫停住函數,並提供當前的返回值
yield和return:
 1.相同點:二者都是在函數內部使用,均可以返回值,而且返回值沒有類型和個數的限制
 2.不一樣點:return只能返回一次之;yield能夠返回屢次值


五丶生成器表達式

 * 把列表推導式的[]換成()就是生成器表達式
 * 優勢:省內存,一次只產生一個值在內存中

t = (i for i in range(10))
print(t)
print(f"next(t): {next(t)}")

<generator object <genexpr> at 0x1101c4888> next(t): 0

生成器表達式和列表推導式

列表推導式至關於直接給你一筐蛋,而生成器表達式至關於給你一隻老母雞。

# 生成器表達式
with open('52.txt', 'r', encoding='utf8') as f:
    nums = [len(line) for line in f]

print(max(nums))

1

# 列表推導式
with open('52.txt','r',encoding='utf8') as f:
    nums = (len(line) for line in f)

print(max(nums)) # ValueError: I/O operation on closed file.
相關文章
相關標籤/搜索