生成器進化到協程 Part 2

2021-09-13 16:55:11 字數 4862 閱讀 8927

在 part 1 我們已經介紹了生成器的定義和生成器的操作,現在讓我們開始使用生成器。part 2 主要描述了如何使用yieldcontextmanager建立乙個上下文管理器,並解釋了原理。

理解上下文可以聯想我們做閱讀理解時要解讀文章某處的意思需要閱讀該處前後段落,正是前後文提供了理解的「背景」。而程式的執行的上下文也可以理解為程式在執行時的某些變數,正是這些變數構成了執行環境,讓程式可以完成操作。

python 中的上下文管理器提供這樣一種功能,為你的程式執行時提供乙個特定「空間」,當進入這個空間時 python 上下文管理器 為你做一些準備工作。這個「空間」中一般含有特殊的變數,你在這個「空間」中進行一些操作,然後離開。在你離開時 python 上下文管理器又會幫你做一些收尾工作,保證不會汙染執行環境。

下面是一些常見的**模式

# 讀取檔案

f = open()

# do something

f.close()

# 使用鎖

lock.acquire()

# do somethin

lock.release()

# 進行資料庫操作

db.start_transaction()

# do something

db.commit()

# 對某段**進行計時

start = time.time()

# do something

end = time.time()

如果使用with可以這樣寫:

witn open(filename) as f:

# do something

pass

with lock():

# do something

pass

with語句實際上使用了實現了__enter____exit__方法的上下文管理器類。乙個典型的上下文管理器類如下:

clss contextmanager:

def __enter__(self):

return value

def __exit__(self, exc_type, val, tb):

if exec_type is none:

return

else:

# 處理異常

return true if handled else false

正如方法名明確告訴我們的,__enter__方法負責進入上下的準備工作,如果有需要可以返回乙個值,這個值將會被賦值給with contextmanager() as ret_value中的ret_value__exit__則負責收尾工作,這包括了異常處理。

對於這樣一段**

with contextmanager() as var:

# do something

相當於

ctxmanager = contextmanager()

var = ctxmanager.__enter__()

# do somethin

ctxmanager.__exit__()

乙個可用的例子:

import tempfile

import shutil

class tmpdir:

def __enter__(self):

self.dirname = tempfile.mkdtemp()

return self.dirname

def __exit__(self, exc, val, tb):

shutil.rmtree(self.dirname)

這個上下文管理提供臨時檔案的功能,在with語句結束後會自動刪除臨時資料夾。

with tempdir() as dirname:

# 使用臨時資料夾進行一些操作

pass

關於上面兩個特殊方法的文件可以在 python 文件的 context manager types 找到。另外關於with關鍵字的詳細說明參考 pep 343,不過這篇 pep 不是很好讀,good luck :******_smile:!

能看到這裡的都應該對上下文管理器有所了解,準備好把yield加入我們的上下文管理器**中。

先看乙個例子

import tempfile, shutil

from contextlib import contextmanager

@contextmanager

def tempdir():

outdir = tempfile.mkdtemp()

try:

yield outdir

finally:

shutil.rmtree(outdir)

與使用上下文管理器類的實現方式不同,這裡我們沒有顯式實現__enter____exit__,而是通過contextmanager裝飾器和yield實現,你可以試試這兩種方式是等價的。

要理解上面的**,可以把yield想象為一把剪刀,把這個函式一分為二,上部分相當於__enter__,下部分相當於__exit__。我這樣說大家應該明白了吧。

import tempfile, shutil

from contextlib import contextmanager

@contextmanager

def tempdir():

outdir = tempfile.mkdtemp() #

try: # __enter__

yield outdir #

--cut---╳-----------------------------------

finally: #

shutil.rmtree(outdir) # __exit__

實現「剪刀」功能關鍵在於contextmanager。對於上面的**,我們來一步一步地結構它:

contextmanager其實使用了乙個上下文管理器類,這個類在在初始化時需要提供乙個生成器。

class generatorcm:

def __init__(self, gen):

self.gen = gen

def __enter__(self):

...def __exit__(self, exc, val, tb):

...

contextmanager的實現如下

def contextmanager(func):

def run(*args, **kwargs):

return generatorcm(func(*args, **kwargs))

return run

由於contextmanger所裝飾的函式裡有yield所以我們在呼叫func(*args, **kwargs)時返回的是乙個生成器。要使這個生成器前進,我們需要呼叫next函式

def __enter__(self):

return next(self.gen)

generatorcm__ente__方法會讓生成器前進到yield語句處,並返回產出值。

def __exit__(self, exc, val, tb):

try:

if exc is none:

next(self.gen)

else:

self.gen.throw(exc, val, tb)

raise runtimeerror('generator didn\'t stop')

except stopiteration:

return true

except:

if sys.exc_info()[1] is not val: raise

__exit__函式的邏輯比較複雜,如果沒有傳入異常,首先它會嘗試對生成器呼叫next,正常情況下這會丟擲stopiteration,這個異常會被不做並返回true,告訴直譯器正常退出;如果傳入異常,會使用throwyield處丟擲這個異常;如果有其他未捕捉的錯誤,就重新丟擲該錯誤。

實際的**實現會更加複雜,還有一些異常情況沒有處理

如果你對怎麼實現感興趣,你可以閱讀**或者再一次閱讀 pep 343。

part 2 都是關於上下文管理器的內容,與協程關係不大。但通過這部分我們可以看到yield完全不同的用法,也熟悉了控制流 (control-flow) ,這與 part 3 的非同步處理流程有很大關係。讓我們 part 3 再見。

Python 生成器,協程

生成器可以簡單有效的建立龐大的可迭代物件,而不需要在直接在記憶體中建立儲存整個序列 可以使用生成器推導式或者生成器函式來建立生成器 生成器函式返回資料時使用yield語句,而不是使用return def countdown n print counting down from d n while n...

網路程式設計 協程 2 生成器

知識點 生成器是一種特殊的迭代器,使用關鍵字yield來生成就可以,十分簡單 1 示例 生成器是一種特殊的迭代器,內部實現了 iter 和 next 方法,所以生成器只要要使用關鍵字yield 使用生成器做乙個菲波那切數列 deftest1 num a,b 0,1flag 0 while true ...

Kotlin協程案例 序列生成器

一 使用kotlin協程,寫乙個斐波拉契序列 package cn.kotliner.coroutine.sequence import kotlin.coroutines.experimental.buildsequence author wangdong description fun main...