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

資訊專欄INFORMATION COLUMN

SICP Python 描述 第五章 序列和協(xié)程

leap_frog / 2869人閱讀

摘要:消息向迭代器獲取所表示的底層序列的下一個元素。為了對方法調(diào)用做出回應(yīng),迭代器可以執(zhí)行任何計算來獲取或計算底層數(shù)據(jù)序列的下一個元素。這個迭代器應(yīng)擁有方法,依次返回序列中的每個元素,最后到達(dá)序列末尾時產(chǎn)生異常。

第五章 序列和協(xié)程

來源:Chapter 5: Sequences and Coroutines

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

5.1 引言

在這一章中,我們通過開發(fā)新的工具來處理有序數(shù)據(jù),繼續(xù)討論真實(shí)世界中的應(yīng)用。在第二張中,我們介紹了序列接口,在 Python 內(nèi)置的數(shù)據(jù)類型例如tuplelist中實(shí)現(xiàn)。序列支持兩個操作:獲取長度和由下標(biāo)訪問元素。第三章中,我們開發(fā)了序列接口的用戶定義實(shí)現(xiàn),用于表示遞歸列表的Rlist類。序列類型具有高效的表現(xiàn)力,并且可以讓我們高效訪問大量有序數(shù)據(jù)集。

但是,使用序列抽象表示有序數(shù)據(jù)有兩個重要限制。第一個是長度為n的序列的要占據(jù)比例為n的內(nèi)存總數(shù)。于是,序列越長,表示它所占的內(nèi)存空間就越大。

第二個限制是,序列只能表示已知且長度有限的數(shù)據(jù)集。許多我們想要表示的有序集合并沒有定義好的長度,甚至有些是無限的。兩個無限序列的數(shù)學(xué)示例是正整數(shù)和斐波那契數(shù)。無限長度的有序數(shù)據(jù)集也出現(xiàn)在其它計算領(lǐng)域,例如,所有推特狀態(tài)的序列每秒都在增長,所以并沒有固定的長度。與之類似,經(jīng)過基站發(fā)送出的電話呼叫序列,由計算機(jī)用戶發(fā)出的鼠標(biāo)動作序列,以及飛機(jī)上的傳感器產(chǎn)生的加速度測量值序列,都在世界演化過程中無限擴(kuò)展。

在這一章中,我們介紹了新的構(gòu)造方式用于處理有序數(shù)據(jù),它為容納未知或無限長度的集合而設(shè)計,但僅僅使用有限的內(nèi)存。我們也會討論這些工具如何用于一種叫做協(xié)程的程序結(jié)構(gòu),來創(chuàng)建高效、模塊化的數(shù)據(jù)處理流水線。

5.2 隱式序列

序列可以使用一種程序結(jié)構(gòu)來表示,它不將每個元素顯式儲存在內(nèi)存中,這是高效處理有序數(shù)據(jù)的核心概念。為了將這個概念用于實(shí)踐,我們需要構(gòu)造對象來提供序列中所有元素的訪問,但是不要事先把所有元素計算出來并儲存。

這個概念的一個簡單示例就是第二章出現(xiàn)的range序列類型。range表示連續(xù)有界的整數(shù)序列。但是,它的每個元素并不顯式在內(nèi)存中表示,當(dāng)元素從range中獲取時,才被計算出來。所以,我們可以表示非常大的整數(shù)范圍。只有范圍的結(jié)束位置才被儲存為range對象的一部分,元素都被憑空計算出來。

>>> r = range(10000, 1000000000)
>>> r[45006230]
45016230

這個例子中,當(dāng)構(gòu)造范圍示例時,并不是這個范圍內(nèi)的所有 999,990,000 個整數(shù)都被儲存。反之,范圍對象將第一個元素 10,000 與下標(biāo)相加 45,006,230 來產(chǎn)生第 45,016,230 個元素。計算所求的元素值并不從現(xiàn)有的表示中獲取,這是惰性計算的一個例子。計算機(jī)科學(xué)將惰性作為一種重要的計算工具加以贊揚(yáng)。

迭代器是提供底層有序數(shù)據(jù)集的有序訪問的對象。迭代器在許多編程語言中都是內(nèi)建對象,包括 Python。迭代器抽象擁有兩個組成部分:一種獲取底層元素序列的下一個元素的機(jī)制,以及一種標(biāo)識元素序列已經(jīng)到達(dá)末尾,沒有更多剩余元素的機(jī)制。在帶有內(nèi)建對象系統(tǒng)的編程語言中,這個抽象通常相當(dāng)于可以由類實(shí)現(xiàn)的特定接口。Python 的迭代器接口會在下一節(jié)中描述。

迭代器的實(shí)用性來源于一個事實(shí),底層數(shù)據(jù)序列并不能顯式在內(nèi)存中表達(dá)。迭代器提供了一種機(jī)制,可以依次計算序列中的每個值,但是所有元素不需要連續(xù)儲存。反之,當(dāng)下個元素從迭代器獲取的時候,這個元素會按照請求計算,而不是從現(xiàn)有的內(nèi)存來源中獲取。

范圍可以惰性計算序列中的元素,因?yàn)樾蛄械谋硎臼墙y(tǒng)一的,并且任何元素都可以輕易從范圍的起始和結(jié)束位置計算出來。迭代器支持更廣泛的底層有序數(shù)據(jù)集的惰性生成,因?yàn)樗鼈儾恍枰峁┑讓有蛄腥我庠氐脑L問途徑。反之,它們僅僅需要按照順序,在每次其它元素被請求的時候,計算出序列的下一個元素。雖然不像序列可訪問任意元素那樣靈活(叫做隨機(jī)訪問),有序數(shù)據(jù)序列的順序訪問對于數(shù)據(jù)處理應(yīng)用來說已經(jīng)足夠了。

5.2.1 Python 迭代器

Python 迭代器接口包含兩個消息。__next__消息向迭代器獲取所表示的底層序列的下一個元素。為了對__next__方法調(diào)用做出回應(yīng),迭代器可以執(zhí)行任何計算來獲取或計算底層數(shù)據(jù)序列的下一個元素。__next__的調(diào)用讓迭代器產(chǎn)生變化:它們向前移動迭代器的位置。所以多次調(diào)用__next__會有序返回底層序列的元素。在__next__的調(diào)用過程中,Python 通過StopIteration異常,來表示底層數(shù)據(jù)序列已經(jīng)到達(dá)末尾。

下面的Letters類迭代了從ad字母的底層序列。成員變量current儲存了序列中的當(dāng)前字母。__next__方法返回這個字母,并且使用它來計算current的新值。

>>> class Letters(object):
        def __init__(self):
            self.current = "a"
        def __next__(self):
            if self.current > "d":
                raise StopIteration
            result = self.current
            self.current = chr(ord(result)+1)
            return result
        def __iter__(self):
            return self

__iter__消息是 Python 迭代器所需的第二個消息。它只是簡單返回迭代器,它對于提供迭代器和序列的通用接口很有用,在下一節(jié)會描述。

使用這個類,我們就可以訪問序列中的字母:

>>> letters = Letters()
>>> letters.__next__()
"a"
>>> letters.__next__()
"b"
>>> letters.__next__()
"c"
>>> letters.__next__()
"d"
>>> letters.__next__()
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 12, in next
StopIteration

Letters示例只能迭代一次。一旦__next__()方法產(chǎn)生了StopIteration異常,它就從此之后一直這樣了。除非創(chuàng)建新的實(shí)例,否則沒有辦法來重置它。

迭代器也允許我們表示無限序列,通過實(shí)現(xiàn)永遠(yuǎn)不會產(chǎn)生StopIteration異常的__next__方法。例如,下面展示的Positives類迭代了正整數(shù)的無限序列:

>>> class Positives(object):
        def __init__(self):
            self.current = 0;
        def __next__(self):
            result = self.current
            self.current += 1
            return result
        def __iter__(self):
            return self
5.2.2 for語句

Python 中,序列可以通過實(shí)現(xiàn)__iter__消息用于迭代。如果一個對象表示有序數(shù)據(jù),它可以在for語句中用作可迭代對象,通過回應(yīng)__iter__消息來返回迭代器。這個迭代器應(yīng)擁有__next__()方法,依次返回序列中的每個元素,最后到達(dá)序列末尾時產(chǎn)生StopIteration異常。

>>> counts = [1, 2, 3]
>>> for item in counts:
        print(item)
1
2
3

在上面的實(shí)例中,counts列表返回了迭代器,作為__iter__()方法調(diào)用的回應(yīng)。for語句之后反復(fù)調(diào)用迭代器的__next__()方法,并且每次都將返回值賦給item。這個過程一直持續(xù),直到迭代器產(chǎn)生了StopIteration異常,這時for語句就終止了。

使用我們關(guān)于迭代器的知識,我們可以拿while、賦值和try語句實(shí)現(xiàn)for語句的求值規(guī)則:

>>> i = counts.__iter__()
>>> try:
        while True:
            item = i.__next__()
            print(item)
    except StopIteration:
        pass
1
2
3

在上面,調(diào)用counts__iter__方法所返回的迭代器綁定到了名稱i上面,便于依次獲取每個元素。StopIteration異常的處理子句不做任何事情,但是這個異常的處理提供了退出while循環(huán)的控制機(jī)制。

5.2.3 生成器和yield語句

上面的LettersPositives對象需要我們引入一種新的字段,self.current,來跟蹤序列的處理過程。在上面所示的簡單序列中,這可以輕易實(shí)現(xiàn)。但對于復(fù)雜序列,__next__()很難在計算中節(jié)省空間。生成器允許我們通過利用 Python 解釋器的特性定義更復(fù)雜的迭代。

生成器是由一類特殊函數(shù),叫做生成器函數(shù)返回的迭代器。生成器函數(shù)不同于普通的函數(shù),因?yàn)樗辉诤瘮?shù)體中包含return語句,而是使用yield語句來返回序列中的元素。

生成器不使用任何對象屬性來跟蹤序列的處理過程。它們控制生成器函數(shù)的執(zhí)行,每次__next__方法調(diào)用時,它們執(zhí)行到下一個yield語句。Letters迭代可以使用生成器函數(shù)實(shí)現(xiàn)得更加簡潔。

>>> def letters_generator():
        current = "a"
        while current <= "d":
            yield current
            current = chr(ord(current)+1)
>>> for letter in letters_generator():
        print(letter)
a
b
c
d

即使我們永不顯式定義__iter__()__next__()方法,Python 會理解當(dāng)我們使用yield語句時,我們打算定義生成器函數(shù)。調(diào)用時,生成器函數(shù)并不返回特定的產(chǎn)出值,而是返回一個生成器(一種迭代器),它自己就可以返回產(chǎn)出的值。生成器對象擁有__iter____next__方法,每個對__next__的調(diào)用都會從上次停留的地方繼續(xù)執(zhí)行生成器函數(shù),直到另一個yield語句執(zhí)行的地方。

__next__第一次調(diào)用時,程序從letters_generator的函數(shù)體一直執(zhí)行到進(jìn)入yield語句。之后,它暫停并返回current值。yield語句并不破壞新創(chuàng)建的環(huán)境,而是為之后的使用保留了它。當(dāng)__next__再次調(diào)用時,執(zhí)行在它停留的地方恢復(fù)。letters_generator作用域中current和任何所綁定名稱的值都會在隨后的__next__調(diào)用中保留。

我們可以通過手動調(diào)用__next__()來遍歷生成器:

>>> letters = letters_generator()
>>> type(letters)

>>> letters.__next__()
"a"
>>> letters.__next__()
"b"
>>> letters.__next__()
"c"
>>> letters.__next__()
"d"
>>> letters.__next__()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration

在第一次__next__()調(diào)用之前,生成器并不會開始執(zhí)行任何生成器函數(shù)體中的語句。

5.2.4 可迭代對象

Python 中,迭代只會遍歷一次底層序列的元素。在遍歷之后,迭代器在__next__()調(diào)用時會產(chǎn)生StopIteration異常。許多應(yīng)用需要迭代多次元素。例如,我們需要對一個列表迭代多次來枚舉所有的元素偶對:

>>> def all_pairs(s):
        for item1 in s:
            for item2 in s:
                yield (item1, item2)
>>> list(all_pairs([1, 2, 3]))
[(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)]

序列本身不是迭代器,但是它是可迭代對象。Python 的可迭代接口只包含一個消息,__iter__,返回一個迭代器。Python 中內(nèi)建的序列類型在__iter__方法調(diào)用時,返回迭代器的新實(shí)例。如果一個可迭代對象在每次調(diào)用__iter__時返回了迭代器的新實(shí)例,那么它就能被迭代多次。

新的可迭代類可以通過實(shí)現(xiàn)可迭代接口來定義。例如,下面的可迭代對象LetterIterable類在每次調(diào)用__iter__時返回新的迭代器來迭代字母。

>>> class LetterIterable(object):
        def __iter__(self):
            current = "a"
            while current <= "d":
                yield current
                current = chr(ord(current)+1)

__iter__方法是個生成器函數(shù),它返回一個生成器對象,產(chǎn)出從"a""d"的字母。

Letters迭代器對象在單次迭代之后就被“用完”了,但是LetterIterable對象可被迭代多次。所以,LetterIterable示例可以用于all_pairs的參數(shù)。

>>> letters = LetterIterable()
>>> all_pairs(letters).__next__()
("a", "a")
5.2.5 流

流提供了一種隱式表示有序數(shù)據(jù)的最終方式。流是惰性計算的遞歸列表。就像第三章的Rlist類那樣,Stream實(shí)例可以響應(yīng)對其第一個元素和剩余部分的獲取請求。同樣,Stream的剩余部分還是Stream。然而不像RList,流的剩余部分只在查找時被計算,而不是事先存儲。也就是說流的剩余部分是惰性計算的。

為了完成這個惰性求值,流會儲存計算剩余部分的函數(shù)。無論這個函數(shù)在什么時候調(diào)用,它的返回值都作為流的一部分,儲存在叫做_rest的屬性中。下劃線表示它不應(yīng)直接訪問??稍L問的屬性rest是個方法,它返回流的剩余部分,并在必要時計算它。使用這個設(shè)計,流可以儲存計算剩余部分的方式,而不用總是顯式儲存它們。

>>> class Stream(object):
        """A lazily computed recursive list."""
        def __init__(self, first, compute_rest, empty=False):
            self.first = first
            self._compute_rest = compute_rest
            self.empty = empty
            self._rest = None
            self._computed = False
        @property
        def rest(self):
            """Return the rest of the stream, computing it if necessary."""
            assert not self.empty, "Empty streams have no rest."
            if not self._computed:
                self._rest = self._compute_rest()
                self._computed = True
            return self._rest
        def __repr__(self):
            if self.empty:
                return ""
            return "Stream({0}, )".format(repr(self.first))
>>> Stream.empty = Stream(None, None, True)

遞歸列表可使用嵌套表達(dá)式來定義。例如,我們可以創(chuàng)建RList,來表達(dá)15的序列,像下面這樣:

>>> r = Rlist(1, Rlist(2+3, Rlist.empty))

與之類似,我們可以創(chuàng)建一個Stream來表示相同序列。Stream在請求剩余部分之前,并不會實(shí)際計算下一個元素5

>>> s = Stream(1, lambda: Stream(2+3, lambda: Stream.empty))

這里,1是流的第一個元素,后面的lambda表達(dá)式是用于計算流的剩余部分的函數(shù)。被計算的流的第二個元素又是一個返回空流的函數(shù)。

訪問遞歸列表r和流s中的元素?fù)碛邢嗨频倪^程。但是,5儲存在了r之中,而對于s來說,它在首次被請求時通過加法來按要求計算。

>>> r.first
1
>>> s.first
1
>>> r.rest.first
5
>>> s.rest.first
5
>>> r.rest
Rlist(5)
>>> s.rest
Stream(5, )

當(dāng)make_integer_stream首次被調(diào)用時,它返回了一個流,流的first是序列中第一個整數(shù)(默認(rèn)為1)。但是,make_integer_stream實(shí)際是遞歸的,因?yàn)檫@個流的compute_rest以自增的參數(shù)再次調(diào)用了make_integer_stream。這會讓make_integer_stream變成遞歸的,同時也是惰性的。

>>> ints.first
1
>>> ints.rest.first
2
>>> ints.rest.rest
Stream(3, )

無論何時請求整數(shù)流的rest,都僅僅遞歸調(diào)用make_integer_stream。

操作序列的相同高階函數(shù) -- mapfilter -- 同樣可應(yīng)用于流,雖然它們的實(shí)現(xiàn)必須修改來惰性調(diào)用它們的參數(shù)函數(shù)。map_stream在一個流上映射函數(shù),這會產(chǎn)生一個新的流。局部定義的compute_rest函數(shù)確保了無論什么時候rest被計算出來,這個函數(shù)都會在流的剩余部分上映射。

>>> def map_stream(fn, s):
        if s.empty:
            return s
        def compute_rest():
            return map_stream(fn, s.rest)
        return Stream(fn(s.first), compute_rest)

流可以通過定義compute_rest函數(shù)來過濾,這個函數(shù)在流的剩余部分上調(diào)用過濾器函數(shù)。如果過濾器函數(shù)拒絕了流的第一個元素,剩余部分會立即計算出來。因?yàn)?b>filter_stream是遞歸的,剩余部分可能會多次計算直到找到了有效的first元素。

>>> def filter_stream(fn, s):
        if s.empty:
            return s
        def compute_rest():
            return filter_stream(fn, s.rest)
        if fn(s.first):
            return Stream(s.first, compute_rest)
        return compute_rest()

map_streamfilter_stream展示了流式處理的常見模式:無論流的剩余部分何時被計算,局部定義的compute_rest函數(shù)都會對流的剩余部分遞歸調(diào)用某個處理函數(shù)。

為了觀察流的內(nèi)容,我們需要將其截斷為有限長度,并轉(zhuǎn)換為 Python list。

>>> def truncate_stream(s, k):
        if s.empty or k == 0:
            return Stream.empty
        def compute_rest():
            return truncate_stream(s.rest, k-1)
        return Stream(s.first, compute_rest)
>>> def stream_to_list(s):
        r = []
        while not s.empty:
            r.append(s.first)
            s = s.rest
        return r

這些便利的函數(shù)允許我們驗(yàn)證map_stream的實(shí)現(xiàn),使用一個非常簡單的例子,從37的整數(shù)平方。

>>> s = make_integer_stream(3)
>>> s
Stream(3, )
>>> m = map_stream(lambda x: x*x, s)
>>> m
Stream(9, )
>>> stream_to_list(truncate_stream(m, 5))
[9, 16, 25, 36, 49]

我們可以使用我們的filter_stream函數(shù)來定義素數(shù)流,使用埃拉托斯特尼篩法(sieve of Eratosthenes),它對整數(shù)流進(jìn)行過濾,移除第一個元素的所有倍數(shù)數(shù)值。通過成功過濾出每個素數(shù),所有合數(shù)都從流中移除了。

>>> def primes(pos_stream):
        def not_divible(x):
            return x % pos_stream.first != 0
        def compute_rest():
            return primes(filter_stream(not_divible, pos_stream.rest))
        return Stream(pos_stream.first, compute_rest)

通過截斷primes流,我們可以枚舉素數(shù)的任意前綴:

>>> p1 = primes(make_integer_stream(2))
>>> stream_to_list(truncate_stream(p1, 7))
[2, 3, 5, 7, 11, 13, 17]

流和迭代器不同,因?yàn)樗鼈兛梢远啻蝹鬟f給純函數(shù),并且每次都產(chǎn)生相同的值。素數(shù)流并沒有在轉(zhuǎn)換為列表之后“用完”。也就是說,在將流的前綴轉(zhuǎn)換為列表之后,p1的第一個元素仍舊是2。

>>> p1.first
2

就像遞歸列表提供了序列抽象的簡單實(shí)現(xiàn),流提供了簡單、函數(shù)式的遞歸數(shù)據(jù)結(jié)構(gòu),它通過高階函數(shù)的使用實(shí)現(xiàn)了惰性求值。

5.3 協(xié)程

這篇文章的大部分專注于將復(fù)雜程序解構(gòu)為小型、模塊化組件的技巧。當(dāng)一個帶有復(fù)雜行為的函數(shù)邏輯劃分為幾個獨(dú)立的、本身為函數(shù)的步驟時,這些函數(shù)叫做輔助函數(shù)或者子過程。子過程由主函數(shù)調(diào)用,主函數(shù)負(fù)責(zé)協(xié)調(diào)子函數(shù)的使用。

這一節(jié)中,我們使用協(xié)程,引入了一種不同的方式來解構(gòu)復(fù)雜的計算。它是一種針對有序數(shù)據(jù)的任務(wù)處理方式。就像子過程那樣,協(xié)程會計算復(fù)雜計算的一小步。但是,在使用協(xié)程時,沒有主函數(shù)來協(xié)調(diào)結(jié)果。反之,協(xié)程會自發(fā)鏈接到一起來組成流水線。可能有一些協(xié)程消耗輸入數(shù)據(jù),并把它發(fā)送到其它協(xié)程。也可能有一些協(xié)程,每個都對發(fā)送給它的數(shù)據(jù)執(zhí)行簡單的處理步驟。最后可能有另外一些協(xié)程輸出最終結(jié)果。

協(xié)程和子過程的差異是概念上的:子過程在主函數(shù)中位于下級,但是協(xié)程都是平等的,它們協(xié)作組成流水線,不帶有任何上級函數(shù)來負(fù)責(zé)以特定順序調(diào)用它們。

這一節(jié)中,我們會學(xué)到 Python 如何通過yieldsend()語句來支持協(xié)程的構(gòu)建。之后,我們會看到協(xié)程在流水線中的不同作用,以及協(xié)程如何支持多任務(wù)。

5.3.1 Python 協(xié)程

在之前一節(jié)中,我們介紹了生成器函數(shù),它使用yield來返回一個值。Python 的生成器函數(shù)也可以使用(yield)語句來接受一個值。生成器對象上有兩個額外的方法:send()close(),創(chuàng)建了一個模型使對象可以消耗或產(chǎn)出值。定義了這些對象的生成器函數(shù)叫做協(xié)程。

協(xié)程可以通過(yield)語句來消耗值,向像下面這樣:

value = (yield)

使用這個語法,在帶參數(shù)調(diào)用對象的send方法之前,執(zhí)行流會停留在這條語句上。

coroutine.send(data)

之后,執(zhí)行會恢復(fù),value會被賦為data的值。為了發(fā)射計算終止的信號,我們需要使用close()方法來關(guān)閉協(xié)程。這會在協(xié)程內(nèi)部產(chǎn)生GeneratorExit異常,它可以由try/except子句來捕獲。

下面的例子展示了這些概念。它是一個協(xié)程,用于打印匹配所提供的模式串的字符串。

>>> def match(pattern):
        print("Looking for " + pattern)
        try:
            while True:
                s = (yield)
                if pattern in s:
                    print(s)
        except GeneratorExit:
            print("=== Done ===")

我們可以使用一個模式串來初始化它,之后調(diào)用__next__()來開始執(zhí)行:

>>> m = match("Jabberwock")
>>> m.__next__()
Looking for Jabberwock

__next__()的調(diào)用會執(zhí)行函數(shù)體,所以"Looking for jabberwock"會被打印。語句會一直持續(xù)執(zhí)行,直到遇到line = (yield)語句。之后,執(zhí)行會暫停,并且等待一個發(fā)送給m的值。我們可以使用send來將值發(fā)送給它。

>>> m.send("the Jabberwock with eyes of flame")
the Jabberwock with eyes of flame
>>> m.send("came whiffling through the tulgey wood")
>>> m.send("and burbled as it came")
>>> m.close()
=== Done ===

當(dāng)我們以一個值調(diào)用m.send時,協(xié)程m內(nèi)部的求值會在line = (yield)語句處恢復(fù),這里會把發(fā)送的值賦給line變量。m中的語句會繼續(xù)求值,如果匹配的話會打印出那一行,并繼續(xù)執(zhí)行循環(huán),直到再次進(jìn)入line = (yield)。之后,m中的求值會暫停,并在m.send調(diào)用后恢復(fù)。

我們可以將使用send()yield的函數(shù)鏈到一起來完成復(fù)雜的行為。例如,下面的函數(shù)將名為text的字符串分割為單詞,并把每個單詞發(fā)送給另一個協(xié)程。

每個單詞都發(fā)送給了綁定到next_coroutine的協(xié)程,使next_coroutine開始執(zhí)行,而且這個函數(shù)暫停并等待。它在next_coroutine暫停之前會一直等待,隨后這個函數(shù)恢復(fù)執(zhí)行,發(fā)送下一個單詞或執(zhí)行完畢。

如果我們將上面定義的match和這個函數(shù)鏈到一起,我們就可以創(chuàng)建出一個程序,只打印出匹配特定單詞的單詞。

>>> text = "Commending spending is offending to people pending lending!"
>>> matcher = match("ending")
>>> matcher.__next__()
Looking for ending
>>> read(text, matcher)
Commending
spending
offending
pending
lending!
=== Done ===

read函數(shù)向協(xié)程matcher發(fā)送每個單詞,協(xié)程打印出任何匹配pattern的輸入。在matcher協(xié)程中,s = (yield)一行等待每個發(fā)送進(jìn)來的單詞,并且在執(zhí)行到這一行之后將控制流交還給read

5.3.2 生產(chǎn)、過濾和消耗

協(xié)程基于如何使用yieldsend()而具有不同的作用:

生產(chǎn)者創(chuàng)建序列中的物品,并使用send(),而不是(yield)。

過濾器使用(yield)來消耗物品并將結(jié)果使用send()發(fā)送給下一個步驟。

消費(fèi)者使用(yield)來消耗物品,但是從不發(fā)送。

上面的read函數(shù)是一個生產(chǎn)者的例子。它不使用(yield),但是使用send來生產(chǎn)數(shù)據(jù)。函數(shù)match是個消費(fèi)者的例子。它不使用send發(fā)送任何東西,但是使用(yield)來消耗數(shù)據(jù)。我們可以將match拆分為過濾器和消費(fèi)者。過濾器是一個協(xié)程,只發(fā)送與它的模式相匹配的字符串。

>>> def match_filter(pattern, next_coroutine):
        print("Looking for " + pattern)
        try:
            while True:
                s = (yield)
                if pattern in s:
                    next_coroutine.send(s)
        except GeneratorExit:
            next_coroutine.close()

消費(fèi)者是一個函數(shù),只打印出發(fā)送給它的行:

>>> def print_consumer():
        print("Preparing to print")
        try:
            while True:
                line = (yield)
                print(line)
        except GeneratorExit:
            print("=== Done ===")

當(dāng)過濾器或消費(fèi)者被構(gòu)建時,必須調(diào)用它的__next__方法來開始執(zhí)行:

>>> printer = print_consumer()
>>> printer.__next__()
Preparing to print
>>> matcher = match_filter("pend", printer)
>>> matcher.__next__()
Looking for pend
>>> read(text, matcher)
spending
pending
=== Done ===

即使名稱filter暗示移除元素,過濾器也可以轉(zhuǎn)換元素。下面的函數(shù)是個轉(zhuǎn)換元素的過濾器的示例。它消耗字符串并發(fā)送一個字典,包含了每個不同的字母在字符串中的出現(xiàn)次數(shù)。

>>> def count_letters(next_coroutine):
        try:
            while True:
                s = (yield)
                counts = {letter:s.count(letter) for letter in set(s)}
                next_coroutine.send(counts)
        except GeneratorExit as e:
            next_coroutine.close()

我們可以使用它來計算文本中最常出現(xiàn)的字母,并使用一個消費(fèi)者,將字典合并來找出最常出現(xiàn)的鍵。

>>> def sum_dictionaries():
        total = {}
        try:
            while True:
                counts = (yield)
                for letter, count in counts.items():
                    total[letter] = count + total.get(letter, 0)
        except GeneratorExit:
            max_letter = max(total.items(), key=lambda t: t[1])[0]
            print("Most frequent letter: " + max_letter)

為了在文件上運(yùn)行這個流水線,我們必須首先按行讀取文件。之后,將結(jié)果發(fā)送給count_letters,最后發(fā)送給sum_dictionaries。我們可以服用read協(xié)程來讀取文件中的行。

>>> s = sum_dictionaries()
>>> s.__next__()
>>> c = count_letters(s)
>>> c.__next__()
>>> read(text, c)
Most frequent letter: n
5.3.3 多任務(wù)

生產(chǎn)者或過濾器并不受限于唯一的下游。它可以擁有多個協(xié)程作為它的下游,并使用send()向它們發(fā)送數(shù)據(jù)。例如,下面是read的一個版本,向多個下游發(fā)送字符串中的單詞:

>>> def read_to_many(text, coroutines):
        for word in text.split():
            for coroutine in coroutines:
                coroutine.send(word)
        for coroutine in coroutines:
            coroutine.close()

我們可以使用它來檢測多個單詞中的相同文本:

>>> m = match("mend")
>>> m.__next__()
Looking for mend
>>> p = match("pe")
>>> p.__next__()
Looking for pe
>>> read_to_many(text, [m, p])
Commending
spending
people
pending
=== Done ===
=== Done ===

首先,read_to_manym上調(diào)用了send(word)。這個協(xié)程正在等待循環(huán)中的text = (yield),之后打印出所發(fā)現(xiàn)的匹配,并且等待下一個send。之后執(zhí)行流返回到了read_to_many,它向p發(fā)送相同的行。所以,text中的單詞會按照順序打印出來。

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

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

相關(guān)文章

  • Python--Redis實(shí)戰(zhàn):五章:使用Redis構(gòu)建支持程序:第2節(jié):計數(shù)器和統(tǒng)計數(shù)據(jù)

    摘要:清理程序通過對記錄已知計數(shù)器的有序集合執(zhí)行命令來一個接一個的遍歷所有已知的計數(shù)器。 上一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第1節(jié):使用Redis來記錄日志下一篇文章:Python--Redis實(shí)戰(zhàn):第五章:使用Redis構(gòu)建支持程序:第3節(jié):查找IP所屬城市以及國家 正如第三章所述,通過記錄各個頁面的被訪問次數(shù),我們可以根據(jù)基本的訪問計數(shù)信息...

    sourcenode 評論0 收藏0
  • python協(xié)程與golang協(xié)程的區(qū)別

    摘要:進(jìn)程線程和協(xié)程進(jìn)程的定義進(jìn)程,是計算機(jī)中已運(yùn)行程序的實(shí)體。協(xié)程和線程的關(guān)系協(xié)程是在語言層面實(shí)現(xiàn)對線程的調(diào)度,避免了內(nèi)核級別的上下文消耗。和都引入了消息調(diào)度系統(tǒng)模型,來避免鎖的影響和進(jìn)程線程開銷大的問題。 進(jìn)程、線程和協(xié)程 進(jìn)程的定義: 進(jìn)程,是計算機(jī)中已運(yùn)行程序的實(shí)體。程序本身只是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程才是程序的真正運(yùn)行實(shí)例。 線程的定義: 操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單...

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

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

    tuniutech 評論0 收藏0
  • Python基礎(chǔ)教程第二版》五章-條件、循環(huán)和其他語句(一)

    摘要:所解包的序列中的元素數(shù)量必須和賦值符號左邊的變量數(shù)量完全一致。其中,冒號標(biāo)識語句塊開始塊中每一個語句都是縮進(jìn)相同量退回到和已經(jīng)閉合的塊一樣的縮進(jìn)量時,表示當(dāng)前塊結(jié)束。成員資格運(yùn)算符字符串和序列比較字符串可按照字母順序比較。 print和import print打印多個表達(dá)式,用逗號,隔開 print abc:, 42, nonono #輸出在每個參數(shù)之間添加空格 print在結(jié)尾處加上...

    宋華 評論0 收藏0

發(fā)表評論

0條評論

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