1. 程式人生 > >多工——協程

多工——協程

迭代

利用for迴圈遍歷取值的過程就被稱為迭代。同時,使用for迴圈遍歷取值的物件被稱為可迭代物件。
判斷一個物件是否是某種型別(可迭代物件)的方法:
	from collections import Iterable
	result = isinstance(需判斷物件,Iterable)

自定義可迭代物件

自定義可迭代物件,在類裡面定義__iter__方法建立的物件就是可迭代物件。

from collections import Iterable

# 在類的裡面定義__iter__方法即是可迭代物件
class MyList(object):
    def __init__(self):
        self.list = list()

    # 新增指定元素
    def append_item(self,item):
        self.list.append(item)

    def __iter__(self):
        pass
    # 可迭代物件的本質,遍歷可迭代物件獲得的其實是獲得的可迭代物件的迭代器,然後通過迭代器獲得物件中的資料。

mylist = MyList()
mylist.append_item(1)
mylist.append_item(2)
result = isinstance(mylist,Iterable)
print(result)

for i in mylist:
    print(i)

執行結果:

Traceback (most recent call last):
True
  File "/Users/hbin/Desktop/untitled/aa.py", line 24, in <module>
    for value in my_list:
TypeError: iter() returned non-iterator of type 'NoneType'
通過執行結果可以看出來,遍歷可迭代物件依次獲取資料需要迭代器

迭代器

1. 自定義迭代器物件
自定義迭代器物件: 在類裡面定義__iter__和__next__方法建立的物件就是迭代器物件

from collections import Iterable
from collections import Iterator

# 自定義可迭代物件: 在類裡面定義__iter__方法建立的物件就是可迭代物件
class MyList(object):

    def __init__(self):
        self.my_list = list()

    # 新增指定元素
    def append_item(self, item):
        self.my_list.append(item)

    def __iter__(self):
        # 可迭代物件的本質:遍歷可迭代物件的時候其實獲取的是可迭代物件的迭代器, 然後通過迭代器獲取物件中的資料
        my_iterator = MyIterator(self.my_list)
        return my_iterator


# 自定義迭代器物件: 在類裡面定義__iter__和__next__方法建立的物件就是迭代器物件
class MyIterator(object):

    def __init__(self, my_list):
        self.my_list = my_list

        # 記錄當前獲取資料的下標
        self.current_index = 0

        # 判斷當前物件是否是迭代器
        result = isinstance(self, Iterator)
        print("MyIterator建立的物件是否是迭代器:", result)

    def __iter__(self):
        return self

    # 獲取迭代器中下一個值
    def __next__(self):
        if self.current_index < len(self.my_list):
            self.current_index += 1
            return self.my_list[self.current_index - 1]
        else:
            # 資料取完了,需要丟擲一個停止迭代的異常
            raise StopIteration


my_list = MyList()
my_list.append_item(1)
my_list.append_item(2)
result = isinstance(my_list, Iterable)

print(result)

for value in my_list:
    print(value)
執行結果:

True
MyIterator建立的物件是否是迭代器: True
1
2

用迭代器實現斐波那契數列

	class Fibonacci(object):

    def __init__(self, num):
        # num:表示生成多少fibonacci數字
        self.num = num
        # 記錄fibonacci前兩個值
        self.a = 0
        self.b = 1
        # 記錄當前生成數字的索引
        self.current_index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_index < self.num:
            result = self.a
            self.a, self.b = self.b, self.a + self.b
            self.current_index += 1
            return result
        else:
            raise StopIteration


fib = Fibonacci(5)
# value = next(fib)
# print(value)

for value in fib:
    print(value)
執行結果:

0
1
1
2
3

生成器

生成器是一種特殊的迭代器,只是不需要在類中定義__iter__,__next__方法,使用更加方便,但是同樣可以使用next函式和for迴圈取值。

建立生成器的方法1

將列表生成式的[]改為()
# 建立生成器
my_generator = (i * 2 for i in range(5))
print(my_generator)

# next獲取生成器下一個值
# value = next(my_generator)
#
# print(value)
for value in my_generator:
    print(value)

建立生成器方式2

在def函式裡面看到yield關鍵字就是生成器def fibonacci(num):
a = 0
b = 1
# 記錄生成fibonacci數字的下標
current_index = 0
print("--11---")
while current_index < num:
    result = a
    a, b = b, a + b
    current_index += 1
    print("--22---")
    # 程式碼執行到yield會暫停,然後把結果返回出去,下次啟動生成器會在暫停的位置繼續往下執行
    yield result
    print("--33---")

fib = fibonacci(5)
value = next(fib)
print(value)
value = next(fib)
print(value)
在生成器的實現方式中,我們將原本在迭代器中定義的__next__方法中實現的基本邏輯放到一個函式中實現,但是將每次迭代返回數值的return換成yield,此時定義的函式就是迭代器。
如果在生成器函式中的yield換成return,語法上沒有問題,但是程式碼執行到return會停止迭代,return只能返回一次值,值可以在捕獲異常的value中輸出,並且丟擲迭代i停止異常。

可以使用send方法啟動生成器並傳參,注意如果第一次啟動生成器,send的引數只能是None,一般來說第一次啟動生成器用next()

協程——yield

協程又稱為微執行緒,纖程,也成為使用者級執行緒,再不開闢執行緒的基礎上實現多工,按照一定順序交替執行,通俗理解在一個def裡面看到yield關鍵字表示協程,協程也是實現多工的一種方式。

import time

def work1():
    while True:
        print("----work1---")
        yield
        time.sleep(0.5)

def work2():
    while True:
        print("----work2---")
        yield
        time.sleep(0.5)

def main():
    w1 = work1()
    w2 = work2()
    while True:
        next(w1)
        next(w2)

if __name__ == "__main__":
    main()

執行結果就是交替輸出work1,work2.

協程greenlet

為了更好的利用協程來完成多工,python中的greenlet模組對其進行了封裝,使得人物切換更簡單。
pip  install greenlet

import time
import greenlet


# 任務1
def work1():
    for i in range(5):
        print("work1...")
        time.sleep(0.2)
        # 切換到協程2裡面執行對應的任務
        g2.switch()


# 任務2
def work2():
    for i in range(5):
        print("work2...")
        time.sleep(0.2)
        # 切換到第一個協程執行對應的任務
        g1.switch()


if __name__ == '__main__':
    # 建立協程指定對應的任務
    g1 = greenlet.greenlet(work1)
    g2 = greenlet.greenlet(work2)

    # 切換到第一個協程執行對應的任務
    g1.switch()
執行結果是work1,work2交替輸出。

協程gevent

greenlet已經完成了協程,但是卻依舊需要人工切換,有一個更強大的第三方庫,可以自動切換任務執行,就是gevent。
原理是gevent在裡面封裝了greenlet方法,遇到耗時等操作就完成切換。保證一直有任務在被執行。
import gevent

def work(n):
    for i in range(n):
        # 獲取當前協程
        print(gevent.getcurrent(), i)

g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 5)
g3 = gevent.spawn(work, 5)
g1.join()
g2.join()
g3.join()
注意這段程式碼執行的順序是依次執行,而不是交替執行。
import gevent

def work(n):
    for i in range(n):
        # 獲取當前協程
        print(gevent.getcurrent(), i)
        #用來模擬一個耗時操作,注意不是time模組中的sleep
        gevent.sleep(1)

g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 5)
g3 = gevent.spawn(work, 5)
g1.join()
g2.join()
g3.join()
當有耗時操作的時候,gevent會自動完成協程之間的切換。
此時的耗時操作,不是time模組的sleep,我們可以通過對程式碼打補丁。讓gevent識別其餘的耗時操作。
from gevent import monkey

# 打補丁,讓gevent框架識別耗時操作,比如:time.sleep,網路請求延時
monkey.patch_all()