成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

python協(xié)程2:yield from 從入門到精通

vpants / 2797人閱讀

摘要:于此同時,會阻塞,等待終止。子生成器返回之后,解釋器會拋出異常,并把返回值附加到異常對象上,只是委派生成器恢復(fù)。實例運行完畢后,返回的值綁定到上。這一部分處理調(diào)用方通過方法傳入的異常。之外的異常會向上冒泡。

上一篇python協(xié)程1:yield的使用介紹了:

生成器作為協(xié)程使用時的行為和狀態(tài)

使用裝飾器預(yù)激協(xié)程

調(diào)用方如何使用生成器對象的 .throw(...) 和 .close() 方法控制協(xié)程

這一篇將介紹:

協(xié)程終止時如何返回值

yield新句法的用途和語義

同時會用幾個協(xié)程的示例展示協(xié)程用法。

讓協(xié)程返回值

先看一個例子:
這段代碼會返回最終均值的結(jié)果,每次激活協(xié)程時不會產(chǎn)出移動平均值,而是最后一次返回。

#! -*- coding: utf-8 -*-

from collections import namedtuple

Result = namedtuple("Result", "count average")


def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break  # 為了返回值,協(xié)程必須正常終止;這里是退出條件
        total += term
        count += 1
        average = total/count
    # 返回一個namedtuple,包含count和average兩個字段。在python3.3前,如果生成器返回值,會報錯
    return Result(count, average)

我們調(diào)用這段代碼,結(jié)果如下

>>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(20) # 并沒有返回值
>>> coro_avg.send(30)
>>> coro_avg.send(40)
>>> coro_avg.send(None) # 發(fā)送None終止循環(huán),導(dǎo)致協(xié)程結(jié)束。生成器對象會拋出StopIteration異常。異常對象的value屬性保存著返回值。
Traceback (most recent call last):
   ...
StopIteration: Result(count=3, average=30)

return 表達式的值會傳給調(diào)用方,賦值給StopIteration 異常的一個屬性。這樣做雖然看著別扭,但為了保留生成器對象耗盡時拋出StopIteration異常的行為,也可以理解。

如果我們想獲取協(xié)程的返回值,可以這么操作:

>>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(20) # 并沒有返回值
>>> coro_avg.send(30)
>>> coro_avg.send(40)
>>> try:
...     coro_avg.send(None)
... except StopIteration as exc:
...     result = exc.value
...
>>> result
Result(count=3, average=30)

看到這我們會說,這是什么鬼,為什么獲取返回值要繞這么一大圈,就沒有簡單的方法嗎?

有的,那就是 yield from

yield from 結(jié)果會在內(nèi)部自動捕獲StopIteration 異常。這種處理方式與 for 循環(huán)處理StopIteration異常的方式一樣。
對于yield from 結(jié)構(gòu)來說,解釋器不僅會捕獲StopIteration異常,還會把value屬性的值變成yield from 表達式的值。

在函數(shù)外部不能使用yield from(yield也不行)。

既然我們提到了 yield from 那yield from 是什么呢?

yield from

yield from 是 Python3.3 后新加的語言結(jié)構(gòu)。和其他語言的await關(guān)鍵字類似,它表示:*在生成器 gen 中使用 yield from subgen()時,subgen 會獲得控制權(quán),把產(chǎn)出的值傳個gen的調(diào)用方,即調(diào)用方可以直接控制subgen。于此同時,gen會阻塞,等待subgen終止。

yield from 可用于簡化for循環(huán)中的yield表達式。

例如:

def gen():
    for c in "AB":
        yield c
    for i in range(1, 3):
        yield i

list(gen())
["A", "B", "1", "2"]

可以改寫為:

def gen():
    yield from "AB"
    yield from range(1, 3)
    

list(gen())
["A", "B", "1", "2"]

下面來看一個復(fù)雜點的例子:(來自Python cookbook 3 ,github源碼地址 https://github.com/dabeaz/python-cookbook/blob/master/src/4/how_to_flatten_a_nested_sequence/example.py)

# Example of flattening a nested sequence using subgenerators

from collections import Iterable

def flatten(items, ignore_types=(str, bytes)):
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, ignore_types):
            yield from flatten(x) # 這里遞歸調(diào)用,如果x是可迭代對象,繼續(xù)分解
        else:
            yield x

items = [1, 2, [3, 4, [5, 6], 7], 8]

# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):
    print(x)

items = ["Dave", "Paula", ["Thomas", "Lewis"]]
for x in flatten(items):
    print(x)

yield from x 表達式對x對象做的第一件事是,調(diào)用 iter(x),獲取迭代器。所以要求x是可迭代對象。

PEP380 的標題是 ”syntax for delegating to subgenerator“(把指責委托給子生成器的句法)。由此我們可以知道,yield from是可以實現(xiàn)嵌套生成器的使用。

yield from 的主要功能是打開雙向通道,把最外層的調(diào)用方與最內(nèi)層的子生成器連接起來,使兩者可以直接發(fā)送和產(chǎn)出值,還可以直接傳入異常,而不用在中間的協(xié)程添加異常處理的代碼。

yield from 包含幾個概念:

委派生成器

包含yield from 表達式的生成器函數(shù)

子生成器

從yield from 部分獲取的生成器。

調(diào)用方

調(diào)用委派生成器的客戶端(調(diào)用方)代碼

這個示意圖是 對yield from 的調(diào)用過程

委派生成器在 yield from 表達式處暫停時,調(diào)用方可以直接把數(shù)據(jù)發(fā)給字生成器,子生成器再把產(chǎn)出的值發(fā)送給調(diào)用方。子生成器返回之后,解釋器會拋出StopIteration異常,并把返回值附加到異常對象上,只是委派生成器恢復(fù)。

這個圖來自于Paul

Sokolovsky 的 How Python 3.3 "yield from" construct works

下邊這個例子是對yield from 的一個應(yīng)用:

 #! -*- coding: utf-8 -*-

from collections import namedtuple


Result = namedtuple("Result", "count average")


# 子生成器
# 這個例子和上邊示例中的 averager 協(xié)程一樣,只不過這里是作為字生成器使用
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        # main 函數(shù)發(fā)送數(shù)據(jù)到這里 
        term = yield
        if term is None: # 終止條件
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average) # 返回的Result 會成為grouper函數(shù)中yield from表達式的值


# 委派生成器
def grouper(results, key):
     # 這個循環(huán)每次都會新建一個averager 實例,每個實例都是作為協(xié)程使用的生成器對象
    while True:
        # grouper 發(fā)送的每個值都會經(jīng)由yield from 處理,通過管道傳給averager 實例。grouper會在yield from表達式處暫停,等待averager實例處理客戶端發(fā)來的值。averager實例運行完畢后,返回的值綁定到results[key] 上。while 循環(huán)會不斷創(chuàng)建averager實例,處理更多的值。
        results[key] = yield from averager()


# 調(diào)用方
def main(data):
    results = {}
    for key, values in data.items():
        # group 是調(diào)用grouper函數(shù)得到的生成器對象,傳給grouper 函數(shù)的第一個參數(shù)是results,用于收集結(jié)果;第二個是某個鍵
        group = grouper(results, key)
        next(group)
        for value in values:
            # 把各個value傳給grouper 傳入的值最終到達averager函數(shù)中;
            # grouper并不知道傳入的是什么,同時grouper實例在yield from處暫停
            group.send(value)
        # 把None傳入groupper,傳入的值最終到達averager函數(shù)中,導(dǎo)致當前實例終止。然后繼續(xù)創(chuàng)建下一個實例。
        # 如果沒有g(shù)roup.send(None),那么averager子生成器永遠不會終止,委派生成器也永遠不會在此激活,也就不會為result[key]賦值
        group.send(None)
    report(results)


# 輸出報告
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(";")
        print("{:2} {:5} averaging {:.2f}{}".format(result.count, group, result.average, unit))


data = {
    "girls;kg":[40, 41, 42, 43, 44, 54],
    "girls;m": [1.5, 1.6, 1.8, 1.5, 1.45, 1.6],
    "boys;kg":[50, 51, 62, 53, 54, 54],
    "boys;m": [1.6, 1.8, 1.8, 1.7, 1.55, 1.6],
}

if __name__ == "__main__":
    main(data)

這段代碼從一個字典中讀取男生和女生的身高和體重。然后把數(shù)據(jù)傳給之前定義的 averager 協(xié)程,最后生成一個報告。

執(zhí)行結(jié)果為

6 boys  averaging 54.00kg
6 boys  averaging 1.68m
6 girls averaging 44.00kg
6 girls averaging 1.58m

這斷代碼展示了yield from 結(jié)構(gòu)最簡單的用法。委派生成器相當于管道,所以可以把任意數(shù)量的委派生成器連接在一起---一個委派生成器使用yield from 調(diào)用一個子生成器,而那個子生成器本身也是委派生成器,使用yield from調(diào)用另一個生成器。最終以一個只是用yield表達式的生成器(或者任意可迭代對象)結(jié)束。

yield from 的意義

PEP380 分6點說明了yield from 的行為。

子生成器產(chǎn)出的值都直接傳給委派生成器的調(diào)用方(客戶端代碼)

使用send() 方法發(fā)給委派生成器的值都直接傳給子生成器。如果發(fā)送的值是None,那么會調(diào)用子生成器的 __next__()方法。如果發(fā)送的值不是None,那么會調(diào)用子生成器的send()方法。如果調(diào)用的方法拋出StopIteration異常,那么委派生成器恢復(fù)運行。任何其他異常都會向上冒泡,傳給委派生成器。

生成器退出時,生成器(或子生成器)中的return expr 表達式會觸發(fā) StopIteration(expr) 異常拋出。

yield from表達式的值是子生成器終止時傳給StopIteration異常的第一個參數(shù)。

傳入委派生成器的異常,除了 GeneratorExit 之外都傳給子生成器的throw()方法。如果調(diào)用throw()方法時拋出 StopIteration 異常,委派生成器恢復(fù)運行。StopIteration之外的異常會向上冒泡。傳給委派生成器。

如果把 GeneratorExit 異常傳入委派生成器,或者在委派生成器上調(diào)用close() 方法,那么在子生成器上調(diào)用close() 方法,如果他有的話。如果調(diào)用close() 方法導(dǎo)致異常拋出,那么異常會向上冒泡,傳給委派生成器;否則,委派生成器拋出 GeneratorExit 異常。

yield from的具體語義很難理解,不過我們可以看下Greg Ewing 的偽代碼,通過偽代碼分析一下:

RESULT = yield from EXPR

# is semantically equivalent to
# EXPR 可以是任何可迭代對象,因為獲取迭代器_i 使用的是iter()函數(shù)。
_i = iter(EXPR)
try:
    _y = next(_i) # 2 預(yù)激字生成器,結(jié)果保存在_y 中,作為第一個產(chǎn)出的值
except StopIteration as _e:
    # 3 如果調(diào)用的方法拋出StopIteration異常,獲取異常對象的value屬性,賦值給_r
    _r = _e.value
else:
    while 1: # 4 運行這個循環(huán)時,委派生成器會阻塞,只能作為調(diào)用方和子生成器直接的通道
        try:
            _s = yield _y # 5 產(chǎn)出子生成器當前產(chǎn)出的元素;等待調(diào)用方發(fā)送_s中保存的值。
        except GeneratorExit as _e:
            # 6 這一部分是用于關(guān)閉委派生成器和子生成器,因為子生成器可以是任意可迭代對象,所以可能沒有close() 方法。
            try:
                _m = _i.close
            except AttributeError:
                pass
            else:
                _m()
            # 如果調(diào)用close() 方法導(dǎo)致異常拋出,那么異常會向上冒泡,傳給委派生成器;否則,委派生成器拋出 GeneratorExit 異常。
            raise _e
        except BaseException as _e: # 7 這一部分處理調(diào)用方通過.throw() 方法傳入的異常。如果子生成器是迭代器,沒有throw()方法,這種情況會導(dǎo)致委派生成器拋出異常
            _x = sys.exc_info()
            try:
                # 傳入委派生成器的異常,除了 GeneratorExit 之外都傳給子生成器的throw()方法。
                _m = _i.throw
            except AttributeError:
                # 子生成器一迭代器,沒有throw()方法, 調(diào)用throw()方法時拋出AttributeError異常傳給委派生成器
                raise _e
            else: # 8
                try:
                    _y = _m(*_x)
                except StopIteration as _e:
                     # 如果調(diào)用throw()方法時拋出 StopIteration 異常,委派生成器恢復(fù)運行。
                     # StopIteration之外的異常會向上冒泡。傳給委派生成器。
                    _r = _e.value
                    break
        else: # 9 如果產(chǎn)出值時沒有異常
            try: # 10 嘗試讓子生成器向前執(zhí)行
                if _s is None: 
                    # 11. 如果發(fā)送的值是None,那么會調(diào)用子生成器的 __next__()方法。
                    _y = next(_i)
                else:
                    # 11. 如果發(fā)送的值不是None,那么會調(diào)用子生成器的send()方法。
                    _y = _i.send(_s)
            except StopIteration as _e: # 12
                # 2. 如果調(diào)用的方法拋出StopIteration異常,獲取異常對象的value屬性,賦值給_r, 退出循環(huán),委派生成器恢復(fù)運行。任何其他異常都會向上冒泡,傳給委派生成器。
                _r = _e.value 
                break
RESULT = _r #13 返回的結(jié)果是 _r 即整個yield from表達式的值

上段代碼變量說明:

_i 迭代器(子生成器)

_y 產(chǎn)出的值 (子生成器產(chǎn)出的值)

_r 結(jié)果 (最終的結(jié)果 即整個yield from表達式的值)

_s 發(fā)送的值 (調(diào)用方發(fā)給委派生成器的值,這個只會傳給子生成器)

_e 異常 (異常對象)

我們可以看到在代碼的第一個 try 部分 使用 _y = next(_i) 預(yù)激了子生成器。這可以看出,上一篇我們使用的用于自動預(yù)激的裝飾器與yield from 語句不兼容。

除了這段偽代碼之外,PEP380 還有個說明:

In a generator, the statement

return value

is semantically equivalent to

raise StopIteration(value)

except that, as currently, the exception cannot be caught by except clauses within the returning generator.

這也就是為什么 yield from 可以使用return 來返回值而 yield 只能使用 try ... except StopIteration ... 來捕獲異常的value 值。

>>> try:
...     coro_avg.send(None)
... except StopIteration as exc:
...     result = exc.value
...
>>> result

到這里,我們已經(jīng)了解了 yield from 的具體細節(jié)。下一篇,會分析一個使用協(xié)程的經(jīng)典案例: 仿真編程。這個案例說明了如何使用協(xié)程在單線程中管理并發(fā)活動。

參考文檔

流暢的python 第16章(這是讀書筆記,這是讀書筆記)

PEP 380-- Syntax for Delegating to a Subgenerator

How Python 3.3 "yield from" construct works

最后,感謝女朋友支持。

>歡迎關(guān)注 >請我喝芬達

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/44437.html

相關(guān)文章

  • python協(xié)程3:用仿真實驗學習協(xié)程

    摘要:徘徊和行程所用的時間使用指數(shù)分布生成,我們將時間設(shè)為分鐘數(shù),以便顯示清楚。迭代表示各輛出租車的進程在各輛出租車上調(diào)用函數(shù),預(yù)激協(xié)程。 前兩篇我們已經(jīng)介紹了python 協(xié)程的使用和yield from 的原理,這一篇,我們用一個例子來揭示如何使用協(xié)程在單線程中管理并發(fā)活動。。 什么是離散事件仿真 Wiki上的定義是: 離散事件仿真將系統(tǒng)隨時間的變化抽象成一系列的離散時間點上的事件,通過...

    banana_pi 評論0 收藏0
  • python協(xié)程1:yield 10分鐘入門

    摘要:協(xié)程定義協(xié)程的底層架構(gòu)是在中定義,并在實現(xiàn)的。為了簡化,我們會使用裝飾器預(yù)激協(xié)程。執(zhí)行上述代碼結(jié)果如下出錯的原因是發(fā)送給協(xié)程的值不能加到變量上。示例使用和方法控制協(xié)程。 最近找到一本python好書《流暢的python》,是到現(xiàn)在為止看到的對python高級特性講述最詳細的一本??戳藚f(xié)程一章,做個讀書筆記,加深印象。 協(xié)程定義 協(xié)程的底層架構(gòu)是在pep342 中定義,并在python2...

    MartinDai 評論0 收藏0
  • Python “黑魔法” 之 Generator Coroutines

    摘要:主程序通過喚起子程序并傳入數(shù)據(jù),子程序處理完后,用將自己掛起,并返回主程序,如此交替進行。通過輪詢或是等事件框架,捕獲返回的事件。從消息隊列中取出記錄,恢復(fù)協(xié)程函數(shù)。然而事實上只有直接操縱的協(xié)程函數(shù)才有可能接觸到這個對象。 首發(fā)于 我的博客 轉(zhuǎn)載請注明出處 寫在前面 本文默認讀者對 Python 生成器 有一定的了解,不了解者請移步至生成器 - 廖雪峰的官方網(wǎng)站。 本文基于 Pyth...

    李文鵬 評論0 收藏0
  • Python中的協(xié)程

    摘要:協(xié)程的基本行為協(xié)程包含四種狀態(tài)等待開始執(zhí)行。協(xié)程中重要的兩個方法調(diào)用方把數(shù)據(jù)提供給協(xié)程。注意使用調(diào)用協(xié)程時會自動預(yù)激,因此與裝飾器不兼容標準庫中的裝飾器不會預(yù)激協(xié)程,因此能兼容句法。因此,終止協(xié)程的本質(zhì)在于向協(xié)程發(fā)送其無法處理的異常。 導(dǎo)語:本文章記錄了本人在學習Python基礎(chǔ)之控制流程篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。 本文重點: 1、掌握協(xié)...

    shinezejian 評論0 收藏0
  • Python中的并發(fā)處理之使用asyncio包

    摘要:并發(fā)用于制定方案,用來解決可能但未必并行的問題。在協(xié)程中使用需要注意兩點使用鏈接的多個協(xié)程最終必須由不是協(xié)程的調(diào)用方驅(qū)動,調(diào)用方顯式或隱式在最外層委派生成器上調(diào)用函數(shù)或方法。對象可以取消取消后會在協(xié)程當前暫停的處拋出異常。 導(dǎo)語:本文章記錄了本人在學習Python基礎(chǔ)之控制流程篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。 本文重點: 1、了解asyncio...

    tuniutech 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<