從今天開始,咱們將開始進入Python的難點,那就是協程
。python
爲了寫明白協程的知識點,我查閱了網上的不少相關資料。發現很難有一個講得系統,講得全面的文章,致使咱們在學習的時候,每每半知半解,學完仍是一臉懵逼。編程
學習協程的第一門課程,是要認識生成器
,有了生成器
的基礎,才能更好地理解協程
。bash
若是你是新手,那麼你應該知道迭代器
,對生成器
應該是比較陌生的吧。不要緊,看完這系列文章,你也能從小白成功過渡爲Python高手。多線程
再次提醒:
本系列全部的代碼均在Python3下編寫,也建議你們儘快投入到Python3的懷抱中來。併發
初學Python的時候,對於這三貨真的是傻傻分不清。甚至還認爲他們是等價的。異步
其實,他們是不同的。異步編程
可迭代的對象,很好理解,咱們很熟悉的:字符串
,list
,dict
,tuple
,deque
等函數
爲了驗證我說的,須要藉助collections.abc
這個模塊(Python2沒有),使用isinstance()
來類別一個對象是不是可迭代的(Iterable
),是不是迭代器(Iterator
),是不是生成器(Generator
)。學習
import collections
from collections.abc import Iterable, Iterator, Generator
# 字符串
astr = "XiaoMing"
print("字符串:{}".format(astr))
print(isinstance(astr, Iterable))
print(isinstance(astr, Iterator))
print(isinstance(astr, Generator))
# 列表
alist = [21, 23, 32,19]
print("列表:{}".format(alist))
print(isinstance(alist, Iterable))
print(isinstance(alist, Iterator))
print(isinstance(alist, Generator))
# 字典
adict = {"name": "小明", "gender": "男", "age": 18}
print("字典:{}".format(adict))
print(isinstance(adict, Iterable))
print(isinstance(adict, Iterator))
print(isinstance(adict, Generator))
# deque
adeque=collections.deque('abcdefg')
print("deque:{}".format(adeque))
print(isinstance(adeque, Iterable))
print(isinstance(adeque, Iterator))
print(isinstance(adeque, Generator))
複製代碼
輸出結果spa
字符串:XiaoMing
True
False
False
列表:[21, 23, 32, 19]
True
False
False
字典:{'name': '小明', 'gender': '男', 'age': 18}
True
False
False
deque:deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
True
False
False
複製代碼
從結果來看,這些可迭代對象都不是迭代器,也不是生成器。它們有一個共同點,就是它們均可以使用for
來循環。這一點,你們都知道,咱們就不去驗證了。
擴展知識:
可迭代對象,是其內部實現了,__iter__
這個魔術方法。
能夠經過,dir()
方法來查看是否有__iter__
來判斷一個變量是不是可迭代的。
接下來是,迭代器
。
對比可迭代對象,迭代器
其實就只是多了一個函數而已。就是__next__()
,咱們能夠再也不使用for
循環來間斷獲取元素值。而能夠直接使用next()方法來實現。
迭代器,是在可迭代的基礎上實現的。要建立一個迭代器,咱們首先,得有一個可迭代對象。
如今就來看看,如何建立一個可迭代對象,並以可迭代對象爲基礎建立一個迭代器。
from collections.abc import Iterable, Iterator, Generator
class MyList(object): # 定義可迭代對象類
def __init__(self, num):
self.end = num # 上邊界
# 返回一個實現了__iter__和__next__的迭代器類的實例
def __iter__(self):
return MyListIterator(self.end)
class MyListIterator(object): # 定義迭代器類
def __init__(self, end):
self.data = end # 上邊界
self.start = 0
# 返回該對象的迭代器類的實例;由於本身就是迭代器,因此返回self
def __iter__(self):
return self
# 迭代器類必須實現的方法,如果Python2則是next()函數
def __next__(self):
while self.start < self.data:
self.start += 1
return self.start - 1
raise StopIteration
if __name__ == '__main__':
my_list = MyList(5) # 獲得一個可迭代對象
print(isinstance(my_list, Iterable)) # True
print(isinstance(my_list, Iterator)) # False
# 迭代
for i in my_list:
print(i)
my_iterator = iter(my_list) # 獲得一個迭代器
print(isinstance(my_iterator, Iterable)) # True
print(isinstance(my_iterator, Iterator)) # True
# 迭代
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
複製代碼
輸出
0
1
2
3
4
True
False
True
True
0
1
2
3
4
複製代碼
若是上面的代碼太多,也能夠看這邊,你更能理解。
from collections.abc import Iterator
aStr = 'abcd' # 建立字符串,它是可迭代對象
aIterator = iter(aStr) # 經過iter(),將可迭代對象轉換爲一個迭代器
print(isinstance(aIterator, Iterator)) # True
next(aIterator) # a
next(aIterator) # b
next(aIterator) # c
next(aIterator) # d
複製代碼
擴展知識:
迭代器,是其內部實現了,__next__
這個魔術方法。(Python3.x)
能夠經過,dir()
方法來查看是否有__next__
來判斷一個變量是不是迭代器的。
接下來,是咱們的重點,生成器
。
生成器的概念在 Python 2.2 中首次出現,之因此引入生成器,是爲了實現一個在計算下一個值時不須要浪費空間的結構。
前面咱們說,迭代器,是在可迭代的基礎上,加了一個next()方法。
而生成器,則是在迭代器的基礎上(能夠用for循環,可使用next()
),再實現了yield
。
yield
是什麼東西呢,它至關於咱們函數裏的return。在每次next(),或者for遍歷的時候,都會yield這裏將新的值返回回去,並在這裏阻塞,等待下一次的調用。正是因爲這個機制,才使用生成器在Python編程中大放異彩。實現節省內存,實現異步編程。
如何建立一個生成器,主要有以下兩種方法
# 使用列表生成式,注意不是[],而是()
L = (x * x for x in range(10))
print(isinstance(L, Generator)) # True
複製代碼
# 實現了yield的函數
def mygen(n):
now = 0
while now < n:
yield now
now += 1
if __name__ == '__main__':
gen = mygen(10)
print(isinstance(gen, Generator)) # True
複製代碼
可迭代對象和迭代器,是將全部的值都生成存放在內存中,而生成器
則是須要元素才臨時生成,節省時間,節省空間。
因爲生成器並非一次生成全部元素,而是一次一次的執行返回,那麼如何刺激生成器執行(或者說激活)呢?
激活主要有兩個方法
next()
generator.send(None)
分別看下例子,你就知道了。
def mygen(n):
now = 0
while now < n:
yield now
now += 1
if __name__ == '__main__':
gen = mygen(4)
# 經過交替執行,來講明這兩種方法是等價的。
print(gen.send(None))
print(next(gen))
print(gen.send(None))
print(next(gen))
複製代碼
輸出
0
1
2
3
複製代碼
生成器在其生命週期中,會有以下四個狀態
GEN_CREATED
# 等待開始執行GEN_RUNNING
# 解釋器正在執行(只有在多線程應用中才能看到這個狀態)GEN_SUSPENDED
# 在yield表達式處暫停GEN_CLOSED
# 執行結束
經過代碼來感覺一下,爲了避免增長代碼理解難度,GEN_RUNNING
這個狀態,我就不舉例了。有興趣的同窗,能夠去嘗試一下多線程。如有疑問,可在後臺回覆我。
from inspect import getgeneratorstate
def mygen(n):
now = 0
while now < n:
yield now
now += 1
if __name__ == '__main__':
gen = mygen(2)
print(getgeneratorstate(gen))
print(next(gen))
print(getgeneratorstate(gen))
print(next(gen))
gen.close() # 手動關閉/結束生成器
print(getgeneratorstate(gen))
複製代碼
輸出
GEN_CREATED
0
GEN_SUSPENDED
1
GEN_CLOSED
複製代碼
在生成器工做過程當中,若生成器不知足生成元素的條件,就會
/應該
拋出異常(StopIteration
)。
經過列表生成式構建的生成器,其內部已經自動幫咱們實現了拋出異常這一步。不信咱們來看一下。
因此咱們在本身定義一個生成器的時候,咱們也應該在不知足生成元素條件的時候,拋出異常。
拿上面的代碼來修改一下。
def mygen(n):
now = 0
while now < n:
yield now
now += 1
raise StopIteration
if __name__ == '__main__':
gen = mygen(2)
next(gen)
next(gen)
next(gen)
複製代碼
經過上面的介紹,咱們知道生成器爲咱們引入了暫停函數執行(yield
)的功能。當有了暫停的功能以後,人們就想能不能在生成器暫停的時候向其發送一點東西(其實上面也有說起:send(None)
)。這種向暫停的生成器發送信息的功能經過 PEP 342
進入 Python 2.5
中,並催生了 Python
中協程
的誕生。根據 wikipedia
中的定義
協程是爲非搶佔式多任務產生子程序的計算機程序組件,協程容許不一樣入口點在不一樣位置暫停或開始執行程序。
注意從本質上而言,協程並不屬於語言中的概念,而是編程模型上的概念。
協程和線程,有類似點
,多個協程之間和線程同樣,只會交叉串行執行;也有不一樣點
,線程之間要頻繁進行切換,加鎖,解鎖,從複雜度和效率來看,和協程相比,這確是一個痛點。協程經過使用 yield
暫停生成器,能夠將程序的執行流程交給其餘的子程序,從而實現不一樣子程序的之間的交替執行。
下面經過一個簡明的演示來看看,如何向生成器中發送消息。
def jumping_range(N):
index = 0
while index < N:
# 經過send()發送的信息將賦值給jump
jump = yield index
if jump is None:
jump = 1
index += jump
if __name__ == '__main__':
itr = jumping_range(5)
print(next(itr))
print(itr.send(2))
print(next(itr))
print(itr.send(-1))
複製代碼
輸出。
0
2
3
2
複製代碼
這裏解釋下爲何這麼輸出。
重點是jump = yield index
這個語句。
分紅兩部分:
yield index
是將index return
給外部調用程序。jump = yield
能夠接收外部程序經過send()發送的信息,並賦值給jump
以上這些,都是講協程併發的基礎必備知識,請必定要親自去實踐並理解它,否則後面的內容,將會變得枯燥無味,晦澀難懂。
下一章,我將講一個Python3.5新引入的語法:yield from
。篇幅也比較多,因此就單獨拿出來說。