1. 程式人生 > 其它 >Python3 協程

Python3 協程

首發地址

迭代是訪問集合元素的一種方式。迭代器是一個可以記住遍歷的位置的物件。迭代器物件從集合的第一個元素開始訪問,直到所有元素被訪問完結束。迭代器只能往前不會後退。

判斷是否可迭代

In [1]: from collections.abc import Iterable

In [2]: isinstance("abc",Iterable)
Out[2]: True

In [3]: isinstance([1,2,3],Iterable)
Out[3]: True

In [4]: isinstance(123,Iterable)
Out[4]: False

返回為True即為可迭代

迭代器

class Classmate(object):
    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
    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
classmate = Classmate()
classmate.add("小1")
classmate.add("小2")
classmate.add("小3")
for i in classmate:
    print(i)

迭代器的應用(斐波那契數列)

class Fibonacci(object):
    def __init__(self,max_num):
        self.max_num = max_num
        self.current_num = 0
        self.front = 0
        self.after = 1
    def __iter__(self):
        return self
    def __next__(self):
        if self.current_num < self.max_num:
            ret = self.front
            self.front, self.after = self.after, (self.front + self.after)
            self.current_num += 1
            return ret
        else:
            raise StopIteration

fibo = Fibonacci(20)
for num in fibo:
    print(num)

迭代器的其他使用方法

並不是只有for迴圈能夠接受可迭代物件,除for迴圈外,list、tuple等也能接受。

a = (11,22,33)
list(a)
#此處list(a)不是單純的型別轉換,而是首先建立一個空列表,然後呼叫next方法,一個一個的往列表中新增。

生成器

生成器是一種特殊的迭代器

建立生成器

方法1

In [1]: nums = [x*2 for x in range(5)]

In [2]: nums
Out[2]: [0, 2, 4, 6, 8]

In [3]: nums = (x*2 for x in range(5))

In [4]: nums
Out[4]: <generator object <genexpr> at 0x000001DC00D7AF20>

方法2

如果一個函式中,存在yield語句,那麼它就不是一個函式而是一個生成器

def create_num(all_num):
    a, b, current_num = 0, 1, 0
    while current_num < all_num:
        yield a
        a, b = b, a + b
        current_num += 1

generator = create_num(100)
#generator就是一個生成器物件
for num in generator:
    print(num)
生成器也是一個特殊的迭代器(使用next函式訪問)
def create_num(all_num):
    a, b, current_num = 0, 1, 0
    while current_num < all_num:
        yield a
        a, b = b, a + b
        current_num += 1
    return "ok ..."

generator = create_num(100)
for i in range(102):
    try:
        print(next(generator))
    except StopIteration as e:
        print(e.value)
        break
send喚醒

send與next的區別,send可以向生成器中傳參

def create_num(all_num):
    a, b, current_num = 0, 1, 0
    while current_num < all_num:
        ret = yield a
        print(ret)
        a, b = b, a + b
        current_num += 1
    return "ok ..."

generator = create_num(100)
print(next(generator))
print(generator.send("傳參"))

第一次啟動生成器,如果使用send,不能傳值。第一次建議使用next,非要使用send,可以傳入None這個空值。

使用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()

使用greenlet完成多工

安裝greenlet

pip install greenlet

greenlet

import time
from greenlet import greenlet


def test1():
    while True:
        print("---A---")
        gr2.switch()
        time.sleep(0.5)

def test2():
    while True:
        print("---B---")
        gr1.switch()
        time.sleep(0.5)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

greenlet已經實現了協程,但是還需要人工切換。gevent可以自動切換

使用gevent完成多工

安裝gevent

pip install gevent

簡單實現

import gevent

def f(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        #只有使用gevent.sleep才會切換
        gevent.sleep(1)

g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)

g1.join()
g2.join()
g3.join()

給程式打補丁

monkey.patch_all()

import gevent
from gevent import monkey
import time

monkey.patch_all()
#monkey.patch_all()給程式打補丁,程式當遇到耗時的程式碼,會換為gevent中自己實現的模組

def f(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        #只有使用gevent.sleep才會切換
        time.sleep(1)

g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)

g1.join()
g2.join()
g3.join()

簡潔寫法

import gevent
from gevent import monkey
import time

monkey.patch_all()
#monkey.patch_all()給程式打補丁,程式當遇到耗時的程式碼,會換為gevent中自己實現的模組

def f(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        #只有使用gevent.sleep才會切換
        time.sleep(1)

gevent.joinall([gevent.spawn(f,5),gevent.spawn(f,5),gevent.spawn(f,5)])

案例-圖片下載器(利用協程提高速度)

import gevent
from gevent import monkey
import urllib.request

monkey.patch_all()
#monkey.patch_all()給程式打補丁,程式當遇到耗時的程式碼,會換為gevent中自己實現的模組

def download_img(url):
    req = urllib.request.urlopen(url)
    with open(url[-8:],'wb') as file:
        file.write(req.read())

def main():
    gevent.joinall([
        gevent.spawn(download_img,"https://assets.ubuntu.com/v1/3887354e-CVE-Priority-icon-High.svg"),
        gevent.spawn(download_img,"https://www.venustech.com.cn/u/cms/www/202106/11174004guxu.png"),
        gevent.spawn(download_img,"https://www.baidu.com/img/flexible/logo/pc/result.png")])

if __name__ == "__main__":
    main()

程序、執行緒、協程對比

  1. 程序是資源分配的單位
  2. 執行緒是作業系統排程的單位
  3. 程序切換需要的資源很大,效率很低
  4. 執行緒切換需要的資源一般,效率一般(不考慮GIL情況下)
  5. 協程切換任務資源很小,效率高
  6. 多程序、多執行緒根據CPU核數不一樣可能是並行,但是協程是在一個執行緒中 所以是併發