Python 生成器和協程

Python3 迭代器與生成器

迭代器

迭代是Python最強大的功能之一,是訪問集合元素的一種方式。python

迭代器是一個能夠記住遍歷的位置的對象。shell

迭代器對象從集合的第一個元素開始訪問,直到全部的元素被訪問完結束。迭代器只能往前不會後退。數據庫

迭代器有兩個基本的方法:iter() 和 next()編程

字符串列表,元組,集合、字典、range()、文件句柄可迭代對象(iterable)均可用於建立迭代器多線程

  • 內部含有__iter__()方法的就是可迭代對象,遵循可迭代協議。
  • 可迭代對象.__iter__() 或者 iter(可迭代對象)化成迭代器
>>> list = [1,2,3,4]
>>> it = iter(list)        # 建立迭代器對象
>>> next(it)               # 輸出迭代器的下一個元素
1
>>> next(it)
2
>>> 

迭代器對象可使用常規for語句進行遍歷:併發

>>> list = ['a', 'b', 'c', 'd']
>>> it = iter(list)	        # 建立迭代器對象
>>> for x in it:
	print(x, end=" ")

	
a b c d 
>>> 

也可使用 next() 函數:app

>>> lst = [2,6,8,9]
>>> it = iter(lst)              # 建立迭代器對象
>>> 
>>> while True:
	try:
		print(next(it))
	except StopIteration:
		break

	
2
6
8
9
>>> 

建立一個迭代器

把一個類做爲一個迭代器使用須要在類中實現兩個方法 __iter__() __next__()ssh

若是你已經瞭解的面向對象編程,就知道類都有一個構造函數,Python 的構造函數爲 __init__(), 它會在對象初始化的時候執行。異步

__iter__() 方法返回一個特殊的迭代器對象, 這個迭代器對象實現了 __next__() 方法並經過 StopIteration 異常標識迭代的完成。

__next__() 方法(Python 2 裏是 next())會返回下一個迭代器對象。

建立一個返回數字的迭代器(計數器),初始值爲 1,逐步遞增 1:async

class Counter:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    x = self.a
    self.a += 1
    return x
 
myclass = Counter()
myiter = iter(myclass)
 
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
# 執行輸出結果爲:
1
2
3
4
5

StopIteration

  StopIteration 異經常使用於標識迭代的完成,防止出現無限循環的狀況,在 __next__() 方法中咱們能夠設置在完成指定循環次數後觸發 StopIteration 異常來結束迭代。

>>> str1 = "Python"
>>> strObj = str1.__iter__()
>>> strObj.__next__()
'P'
>>> strObj.__next__()
'y'
>>> strObj.__next__()
't'
>>> strObj.__next__()
'h'
>>> strObj.__next__()
'o'
>>> strObj.__next__()
'n'
>>> strObj.__next__()
Traceback (most recent call last):
  File "<pyshell#33>", line 1, in <module>
    strObj.__next__()
StopIteration
>>> 

那麼如何判斷一個對象是不是可迭代對象?

  1. 內部是否含有__iter__方法:
  2. 藉助 collectionsIterable,Iterator 判斷類型
>>> tup = (1,2,3)
>>> type(tup)
<class 'tuple'>
>>> dir(tup)        # 帶參數時,返回參數的屬性、方法列表。
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', 
'__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] >>> print('__iter__' in dir(tup)) True >>>
>>> dic = {1:'dict', 2:'str', 3:'list', 4:'tuple', 5:'set', 6:'range()',7:'flie handler'}
>>> isinstance(dic, Iterable)
True
>>> isinstance(dic, Iterator)
False
>>> 
>>> ran = range(6)
>>> type(ran)
<class 'range'>
>>> isinstance(ran, Iterable)
True
>>> isinstance(ran, Iterator)
False
>>> 

生成器

  在 Python 中,使用了 yield 的函數被稱爲生成器generator)。

  跟普通函數不一樣的是,生成器是一個返回迭代器的函數,只能用於迭代操做,更簡單點理解生成器就是一個迭代器

  在調用生成器運行的過程當中,每次遇到 yield 時函數會暫停並保存當前全部的運行信息,返回 yield 的值, 並在下一次執行 next() 方法時從當前位置繼續運行。

  調用一個生成器函數,返回的是一個迭代器對象

  yield Vs return:

 return返回後,函數狀態終止,而yield會保存當前函數的執行狀態,在返回後,函數又回到以前保存的狀態繼續執行。
    • return 終止函數,yield 不會終止生成器函數。
    • 都會返回一個值,return函數的執行者返回值,yield是給next()返回值

  如下實例使用 yield 實現斐波那契數列:

>>> def fib(max):          # 生成器函數 - 斐波那契
	a, b, n = 0, 1, 0
	while n < max:
		yield b    # 使用 yield
		a, b = b, a + b
		n = n + 1

>>> f = fib(6)             # 調用 fab(5) 不會執行 fab 函數,而是返回一個 iterable 對象!
>>> f                      # Python 解釋器會將其視爲一個 generator
<generator object fib at 0x000001C6CB627780>
>>> 
>>> for n in fib(5):
	print(n)

	
1
1
2
3
5
>>> 
>>> f = fib(5)
>>> next(f)         # 使用next函數從生成器中取值,使用next能夠推進生成器的執行
1
>>> next(f)
1
>>> next(f)
2
>>> next(f)
3
>>> next(f)
5
>>> next(f)          # 當函數中已經沒有更多的yield時繼續執行next(g),遇到StopIteration
Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    next(f)
StopIteration
>>> 
>>> fwrong = fib(6)
>>> fwrong.next()      # Python2 中的語法,Python3 會報錯
Traceback (most recent call last):
  File "<pyshell#40>", line 1, in <module>
    fwrong.next()      # Python2 中的語法,Python3 會報錯
AttributeError: 'generator' object has no attribute 'next'
>>> 

  send向生成器中發送數據。send的做用至關於next,只是在驅動生成器繼續執行的同時還能夠向生成器中傳遞數據。

>>> import numbers
>>> def gen_sum():
	total = 0
	while True:
		num = yield
		if isinstance(num, numbers.Integral):
			total += num
			print('total: ', total)
		elif num is None:
			break
	return total

>>> g = gen_sum()
>>> g
<generator object gen_sum at 0x0000026A6703D3B8>
>>> g.send(None)    # 至關於next(g),預激活生成器
>>> g.send(2)
total:  2
>>> g.send(6)
total:  8
>>> g.send(12)
total:  20
>>> g.send(None)    # 中止生成器
Traceback (most recent call last):
  File "<pyshell#40>", line 1, in <module>
    g.send(None)
StopIteration: 20
>>> 
>>> try:
	g.send(None)	# 中止生成器
except StopIteration as e:
	print(e.value)

	
None
>>> 

yield from關鍵字

  yield from 將一個可迭代對象變成一個迭代器返回,也能夠說,yield from關鍵字能夠直接返回一個生成器

>>> def func():
	lst = ['str', 'tuple', 'list', 'dict', 'set']
	yield lst

	
>>> gen = func()
>>> next(gen)
['str', 'tuple', 'list', 'dict', 'set']
>>> for i in gen:
	print(i)

	
>>> # yield from 將一個可迭代對象變成一個迭代器返回
>>> def func2():
	lst = ['str', 'tuple', 'list', 'dict', 'set']
	yield from lst

	
>>> gen2 = func2()
>>> next(gen2)
'str'
>>> next(gen2)
'tuple'
>>> for i in gen2:
	print(i)

	
list
dict
set
>>> 
>>> lst = ['H','e','l']
>>> dic = {'l':'vvvvv','o':'eeeee'}
>>> str1 = 'Python'
>>> 
>>> def yield_gen():
    for i in lst:
        yield i
    for j in dic:
        yield j
    for k in str1:
        yield k

        
>>> for item in yield_gen():
	print(item, end='')

	
HelloPython
>>> 
>>> l = ['H','e','l']
>>> d = {'l':'xxxxx','o':'ooooo'}
>>> s = 'Java'
>>> 
>>> def yield_from_gen():
	yield from l
	yield from d
	yield from s

	
>>> for item in yield_from_gen():
	print(item, end='')

	
HelloJava
>>> 

爲何使用生成器

更容易使用,代碼量較小內存使用更加高效。好比:

  1. 列表是在創建的時候就分配全部的內存空間,
  2. 而生成器僅僅是須要的時候才使用,更像一個記錄表明了一個無限的流。有點像數據庫操做單條記錄使用的遊標
  • 若是咱們要讀取並使用的內容遠遠超過內存,可是須要對全部的流中的內容進行處理,那麼生成器是一個很好的選擇,
  • 好比可讓生成器返回當前的處理狀態,因爲它能夠保存狀態,那麼下一次直接處理便可。

協程

  根據維基百科給出的定義,「協程 是爲非搶佔式多任務產生子程序的計算機程序組件,協程容許不一樣入口點在不一樣位置暫停開始執行程序」。從技術的角度來講,「協程就是你能夠暫停執行的函數」。若是你把它理解成「就像生成器同樣」,那麼你就想對了。

協程,又稱微線程,纖程。英文名Coroutine

協程的概念很早就提出來了,但直到最近幾年纔在某些語言(如Lua)中獲得普遍應用。

# 與多線程、多進程等併發模型不一樣,協程依靠user-space調度,而線程、進程則是依靠kernel來進行調度。
# 線程、進程間切換都須要從用戶態進入內核態,而協程的切換徹底是在用戶態完成,且不像線程進行搶佔式調度,協程是非搶佔式的調度。
# 一般多個運行在同一調度器中的協程運行在一個線程內,這也消除掉了多線程同步等帶來的編程複雜性。同一時刻同一調度器中的協程只有一個會處於運行狀態,這一點很容易從前言得出。
一個一般的誤解是協程不能利用CPU的多核心,經過利用多個線程多個調度器,協程也是能夠用到CPU多核心性能的。

 

協程的定義

協程最先的描述是由Melvin Conway於1958給出「subroutines who act as the master program」(與主程序行爲相似的子例程),此後他又在博士論文中給出了以下定義:

· the values of data local to a coroutine persist between successive calls(協程的局部數據在後續調用中始終保持)

· the execution of a coroutine is suspended as control leaves it, only to carry on where it left off when control re-enters the coroutine at some later stage
(當控制流程離開時,協程的執行被掛起,此後控制流程再次進入這個協程時,這個協程只應從上次離開掛起的地方繼續)。

  

協程的特色在因而一個線程執行,那和多線程比,協程有何優點?

  • 最大的優點就是協程極高的執行效率。
    • 由於子程序切換不是線程切換,而是由程序自身控制,所以,沒有線程切換的開銷,
    • 和多線程比,線程數量越多,協程的性能優點就越明顯。
  • 第二大優點就是不須要多線程的鎖機制
    • 由於只有一個線程,也不存在同時寫變量衝突,
    • 在協程中控制共享資源不加鎖,只須要判斷狀態就行了,因此執行效率比多線程高不少。

由於協程是一個線程執行,那怎麼利用多核CPU呢?

  • 最簡單的方法是多進程+協程,既充分利用多核,又充分發揮協程的高效率,可得到極高的性能。
  • Python對協程的支持是經過generator實現的。

使用yield實現協程

#基於yield實現異步
def consumer():
    '''任務1:接收數據,處理數據'''
    while True:
        x=yield

def producer():
    '''任務2:生產數據'''
    g=consumer()
    next(g)
    for i in range(10000000):
        g.send(i)

producer()

使用yield from實現的協程

import datetime
import heapq    # 堆模塊
import time


class Task:
    def __init__(self, wait_until, coro):
        self.coro = coro
        self.waiting_until = wait_until

    def __eq__(self, other):
        return self.waiting_until == other.waiting_until

    def __lt__(self, other):
        return self.waiting_until < other.waiting_until


class SleepingLoop:

    def __init__(self, *coros):
        self._new = coros
        self._waiting = []

    def run_until_complete(self):
        for coro in self._new:
            wait_for = coro.send(None)
            heapq.heappush(self._waiting, Task(wait_for, coro))
        while self._waiting:
            now = datetime.datetime.now()
            task = heapq.heappop(self._waiting)
            if now < task.waiting_until:
                delta = task.waiting_until - now
                time.sleep(delta.total_seconds())
                now = datetime.datetime.now()
            try:
                print('*'*50)
                wait_until = task.coro.send(now)
                print('-'*50)
                heapq.heappush(self._waiting, Task(wait_until, task.coro))
            except StopIteration:
                pass


def sleep(seconds):
    now = datetime.datetime.now()
    wait_until = now + datetime.timedelta(seconds=seconds)
    print('before yield wait_until')
    actual = yield wait_until   # 返回一個datetime數據類型的時間
    print('after yield wait_until')
    return actual - now


def countdown(label, length, *, delay=0):
    print(label, 'waiting', delay, 'seconds before starting countdown')
    delta = yield from sleep(delay)
    print(label, 'starting after waiting', delta)
    while length:
        print(label, 'T-minus', length)
        waited = yield from sleep(1)
        length -= 1
    print(label, 'lift-off!')


def main():
    loop = SleepingLoop(countdown('A', 5), countdown('B', 3, delay=2),
                        countdown('C', 4, delay=1))
    start = datetime.datetime.now()
    loop.run_until_complete()
    print('Total elapsed time is', datetime.datetime.now() - start)


if __name__ == '__main__':
    main()

  執行結果:

A waiting 0 seconds before starting countdown
before yield wait_until
B waiting 2 seconds before starting countdown
before yield wait_until
C waiting 1 seconds before starting countdown
before yield wait_until
**************************************************
after yield wait_until
A starting after waiting 0:00:00
A T-minus 5
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
C starting after waiting 0:00:01.001511
C T-minus 4
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
A T-minus 4
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
B starting after waiting 0:00:02.000894
B T-minus 3
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
C T-minus 3
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
A T-minus 3
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
B T-minus 2
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
C T-minus 2
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
A T-minus 2
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
B T-minus 1
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
C T-minus 1
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
A T-minus 1
before yield wait_until
--------------------------------------------------
**************************************************
after yield wait_until
B lift-off!
**************************************************
after yield wait_until
C lift-off!
**************************************************
after yield wait_until
A lift-off!
Total elapsed time is 0:00:05.005168

asyncio模塊

  asyncioPython 3.4版本引入的標準庫,直接內置了對異步IO的支持。

  用asyncio提供的@asyncio.coroutine能夠把一個generator標記爲coroutine類型,而後在coroutine內部用yield from調用另外一個coroutine實現異步操做。

  asyncio的編程模型就是一個消息循環。咱們從asyncio模塊中直接獲取一個EventLoop的引用,而後把須要執行的協程扔到EventLoop中執行,就實現了異步IO。

coroutine+yield from
import asyncio

@asyncio.coroutine
def hello():
    print("Nice to learn asyncio.coroutine!")
    # 異步調用asyncio.sleep(1):
    r = yield from asyncio.sleep(1)
    print("Nice to learn asyncio.coroutine again !")


# 獲取EventLoop:
loop = asyncio.get_event_loop()
# 執行coroutine
loop.run_until_complete(hello())
loop.close()
Nice to learn asyncio.coroutine !
Nice to learn asyncio.coroutine again !

 爲了簡化並更好地標識異步IO,從Python 3.5開始引入了新的語法asyncawait,可讓coroutine的代碼更簡潔易讀。

 請注意,async和 await是針對coroutine的新語法,要使用新的語法,只須要作兩步簡單的替換:

  1. @asyncio.coroutine替換爲async
  2. yield from替換爲await
async+await
  在協程函數中,能夠經過await語法來掛起自身的協程,並等待另外一個協程完成直到返回結果:
import asyncio


async def hello():
    print("Nice to learn asyncio.coroutine!")
    # 異步調用asyncio.sleep(1):
    await asyncio.sleep(1)
    print("Nice to learn asyncio.coroutine again !")


# 獲取EventLoop:
loop = asyncio.get_event_loop()
# 執行coroutine
loop.run_until_complete(hello())
loop.close()

執行多個任務

import threading
import asyncio


async def hello():
    print('Hello Python! (%s)' % threading.currentThread())
    await asyncio.sleep(1)
    print('Hello Python again! (%s)' % threading.currentThread())

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

 結果:

Hello Python! (<_MainThread(MainThread, started 4536)>)
Hello Python! (<_MainThread(MainThread, started 4536)>)
Hello Python again! (<_MainThread(MainThread, started 4536)>)
Hello Python again! (<_MainThread(MainThread, started 4536)>)

獲取返回值

import threading
import asyncio


async def hello():
    print('Hello Python! (%s)' % threading.currentThread())
    await asyncio.sleep(1)
    print('Hello Python again! (%s)' % threading.currentThread())
    return "It's done"

loop = asyncio.get_event_loop()
task = loop.create_task(hello())
loop.run_until_complete(task)
ret = task.result()
print(ret)

 結果:

Hello Python! (<_MainThread(MainThread, started 6136)>)
Hello Python again! (<_MainThread(MainThread, started 6136)>)
It's done

執行多個任務獲取返回值

import threading
import asyncio


async def hello(seq):
    print('Hello Python! (%s)' % threading.currentThread())
    await asyncio.sleep(1)
    print('Hello Python again! (%s)' % threading.currentThread())
    return "It's done", seq

loop = asyncio.get_event_loop()
task1 = loop.create_task(hello(2))
task2 = loop.create_task(hello(1))
task_list = [task1, task2]
tasks = asyncio.wait(task_list)
loop.run_until_complete(tasks)

for t in task_list:
    print(t.result())
結果:
Hello Python! (<_MainThread(MainThread, started 12956)>)
Hello Python! (<_MainThread(MainThread, started 12956)>)
Hello Python again! (<_MainThread(MainThread, started 12956)>)
Hello Python again! (<_MainThread(MainThread, started 12956)>)
("It's done", 2)
("It's done", 1)
相關文章
相關標籤/搜索