在 part 1 我們已經介紹了生成器的定義和生成器的操作,現在讓我們開始使用生成器。part 2 主要描述了如何使用yield
和contextmanager
建立乙個上下文管理器,並解釋了原理。
理解上下文可以聯想我們做閱讀理解時要解讀文章某處的意思需要閱讀該處前後段落,正是前後文提供了理解的「背景」。而程式的執行的上下文也可以理解為程式在執行時的某些變數,正是這些變數構成了執行環境,讓程式可以完成操作。
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
,告訴直譯器正常退出;如果傳入異常,會使用throw
在yield
處丟擲這個異常;如果有其他未捕捉的錯誤,就重新丟擲該錯誤。
實際的**實現會更加複雜,還有一些異常情況沒有處理
如果你對怎麼實現感興趣,你可以閱讀**或者再一次閱讀 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...