多工--程序和協程
什麼是程序?
程式在沒執行起來之前是死的,程式執行起來後就是程序,程序跟程式之間的區別就是程序擁有資源,例如登陸QQ之後,QQ可以呼叫音效卡、攝像頭等。
兩個程序之間的通訊,利用佇列Queue,放到記憶體裡,一個寫,一個讀。缺點就是佇列只能在同一個程式或者電腦中執行,要在多臺電腦之間進行,用到快取redis。
import multiprocessing def download_data(q): # 下載資料 data = [11, 22, 33, 44] for i in data: q.put(i) defanalysis_data(q): while True: data = q.get() print(data) if q.empty(): break def main(): q = multiprocessing.Queue(4) p1 = multiprocessing.Process(target=download_data, args=(q, )) p2 = multiprocessing.Process(target=analysis_data, args=(q, )) p1.start() p2.start()if __name__ == "__main__": main()
程序池
程序池裡面有預先指定好的程序數,當很多工一起過來時,如果程序被呼叫完,其他任務就一直等著,等待完成任務的程序來呼叫它們。好處就是減少了程序的建立和銷燬所花的時間和資源。
協程
1、迭代器
在原來的基礎上去得到一個新的東西,這就是迭代。
# 判斷是否可迭代 >>> from collections import Iterable >>> isinstance([11, 22, 33], Iterable) True
from collections import Iterable class Classmatename(): def __init__(self): self.names = list() def add(self, name): self.names.append(name) def __iter__(self): """如果想要一個物件稱為一個 可以迭代的物件,既可以使用for,那麼必須實現__iter__方法""" pass classmatename = Classmatename() classmatename.add("張三") classmatename.add("李四") classmatename.add("王五") print(isinstance(classmatename, Iterable)) # ------》 True
但如何迴圈列印列表裡面的值呢?
首先先想一下當我們迴圈列印列表的時候發生了什麼,每次迴圈,就會列印一個值,下一次迴圈列印下一個值,由此我們應該可以猜想有一個東西,在記錄著我們列印到哪了,這樣才可以在下一次列印接下來的值。對於我們上面建立的類,現在它已經是可以迭代了,但是還沒有一個東西來記錄它應該列印什麼,列印到了哪裡。
for item in xxx_obj: pass 1、判斷xxx_obj是否是可以迭代; 2、在第一步成立的前提下,呼叫iter函式,得到xxx_obj物件的__iter__方法的返回值; 3、__iter__方法的返回值是一個 迭代器
什麼是迭代器?一個物件裡面有“__iter__”方法,我們稱之為 可以迭代。如果"__iter__"方法返回的物件裡面既有“__iter__”又有"__next__"方法,那麼我們稱這個物件為 迭代器。
所以為了可以列印,這裡需要兩個條件:1、有iter值;2、iter值返回一個物件引用。這個物件裡面除了iter方法外還有一個next方法。
因此大體過程就是for迴圈呼叫,首先判斷這個物件是不是可迭代的(有"__iter__",是),接下來自動用iter方法呼叫"__iter__",獲取返回的“物件引用”。接下來for迴圈呼叫這個物件裡面的“__next__”方法,調一次,next方法返回什麼,就輸出什麼給item列印。
from collections import Iterator classmate_Iterator = iter(classmatename) # 用iter獲取迭代器 # 判斷是否是迭代器 print(isinstance(classmate_Iterator, Iterator))
import time class Classmatename(): def __init__(self): self.names = list() self.current_num = 0 def add(self, name): self.names.append(name) def __iter__(self): """如果想要一個物件稱為一個 可以迭代的物件,既可以使用for,那麼必須實現__iter__方法""" # 建立例項物件 return ClassIterator(self) # self指向這個類本身,然後傳給類ClassIterator
class ClassIterator(): def __init__(self, obj): self.obj = obj # self.obj指向例項物件Classmatename self.current_num = 0 def __iter__(self): pass def __next__(self): if self.current_num < len(self.obj.names): ret = self.obj.names[self.current_num] self.current_num += 1 # 注意這裡的self.current_num=0要放在__init__下,如果current_num=0放在__next__下,則for迴圈每次呼叫__next__時,current_num都會被重新記零 return ret else: raise StopIteration # 丟擲異常,告訴for迴圈可以結束了
classmatename = Classmatename() classmatename.add("張三") classmatename.add("李四") classmatename.add("王五") for item in classmatename: print(item) time.sleep(1)
另一種辦法,寫在一起
import time class Classmatename(): def __init__(self): self.names = list() self.current_num = 0 def add(self, name): self.names.append(name) def __iter__(self): """如果想要一個物件稱為一個 可以迭代的物件,既可以使用for,那麼必須實現__iter__方法""" # 建立例項物件 return self # 返回自身給for迴圈呼叫裡面的__next__ def __next__(self): if self.current_num < len(self.names): ret = self.names[self.current_num] self.current_num += 1 return ret else: raise StopIteration # 丟擲異常,告訴for迴圈可以結束了 classmatename = Classmatename() classmatename.add("張三") classmatename.add("李四") classmatename.add("王五") for item in classmatename: print(item) time.sleep(1)
迭代器的優點
優先我們先來了解下Python2中的range和xrange區別
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> xrange(10)
xrange(10)
由上圖可知,range和xrange的區別就是range會直接返回生成的結果,而xrange返回的是生成這個結果的方式,什麼時候需要呼叫裡面的值,它才會去生成,所以xrange佔用的記憶體空間要小很多。(這個問題在Python3已經解決了,Python3的range相當於Python2裡面的xrange)
迭代器也是一樣,生成的是呼叫值的方式,什麼時候要呼叫值,才去生成,因此佔用很少的記憶體空間。
# 用迭代器的方法生成斐波那契數列 class Fibonacci(object): def __init__(self, all_num): self.all_num = all_num self.current_num = 0 self.a = 0 self.b = 1 def __iter__(self): return self def __next__(self): if self.current_num < self.all_num: ret = self.a self.a, self.b = self.b, self.a+self.b self.current_num += 1 return ret else: raise StopIteration fibo = Fibonacci(10) for num in fibo: print(num)
a = (11, 22, 33) list(a) # 首先生成一個空列表,然後迴圈呼叫a裡面的迭代器,把值一個個放到列表中去 [11, 22, 33]
生成器(是一種特殊的迭代器)
生成生成器的第一種方式
>>> nums1 = [x*2 for x in range(10)] >>> nums1 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> nums2 = (x*2 for x in range(10)) >>> nums2 <generator object <genexpr> at 0x00AF5180>
其中,num2就是生成器。可以通過for迴圈遍歷裡面的值。num1和num2的區別就是前面說的,num2不佔用空間,值在需要時才會被生成。
生成生成器的第二種方式
# 用普通函式生成斐波那契數列 def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: print(a) a, b = b, a+b current_num += 1 create_num(10)
用生成器的方法生成斐波那契數列,新增yield
# 生成器 def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: yield a # 如果一個函式中有yield語句,那麼這個就不再是函式,而是一個生成器的模板
a, b = b, a+b current_num += 1 # 如果在呼叫create_num的時候,發現這個函式有yield,那麼不在是呼叫函式,而是建立一個生成器物件 obj = create_num(10)
for num in obj: # for迴圈呼叫過程。首先建立obj,當開始執行for迴圈時,程式開始向下走,
print(num) # 當到達yield a的時候停止,把a的值傳給num,打印出來。然後for繼續呼叫,於是從停止的位置也就是yield那裡繼續向下執行,而不會從函式的開頭重新運行了。
用 "ret = next(obj)" 可以一次呼叫一個值用來驗證下。
def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: yield a a, b = b, a+b current_num += 1 return ".....ok....." obj = create_num(10) while True: try: ret = next(obj) print(ret) except Exception as ret: print(ret.value) # 這個value就是return返回的值 break
第二中呼叫生成器的方法:send() (一般不用做第一次啟動,如果非要,send()裡面只能傳遞None)
def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: ret = yield a print(">>>>>>>>ret>>>>>>>", ret) a, b = b, a+b current_num += 1 obj = create_num(10) ret = next(obj) print(ret) ret = obj.send("hahaha") # 首先程式執行到"yield a"時停止,把“a=0”傳給ret打印出來。接下來執行"send("hahaha")”,從“ret=yield a“開始,此時"yield a"並沒有返回值給等號左邊的ret,那麼就把"hahaha" print(ret) # 傳給ret,由"print(">>>>>>>>>ret>>>>>>>>>", ret)"打印出來。再繼續執行接下來的步驟。
# 結果返回 0 >>ret>>>>>>> hahaha 1
總結:迭代器特點是佔用記憶體空間小,什麼時候用,什麼時候生成;
生成器有迭代器的特點,而且它最大的特點就是可以執行到一半時暫停,返回結果,然後再繼續在原來基礎上繼續執行。
用yield實現多工
import time def task_1(): while True: print("--------1---------") time.sleep(0.1) yield def task_2(): while True: print("--------2---------") time.sleep(0.1) yield def main(): t1 = task_1() t2 = task_2() while True: next(t1) next(t2) if __name__ == "__main__": main()
協程(單程序單執行緒)最大的意義,把原本等待的時間利用起來去做別的事情。
協程依賴於執行緒,執行緒依賴於程序。
有時程式寫了很多行了,用的是time.sleep(),這時不想用gevent裡面的gevent.sleep()方法,可以給程式打補丁
小案例,用gevent實現圖片下載
import urllib.request import gevent from gevent import monkey monkey.patch_all() def download_img(file_name, url): img = urllib.request.urlopen(url) img_content = img.read() with open(file_name, "wb") as f: # 這裡沒有用time模組是因為網路下載過程中本來就會延時,相當於我們前面例項的time.sleep() f.write(img_content) def main(): gevent.joinall([ gevent.spawn(download_img, "1.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2018/08/31/3279944_20180831104533_small.jpg"), gevent.spawn(download_img, "2.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2018/11/14/910907_20181114154402_small.jpg") ]) if __name__ == "__main__": main()