1. contextlib背景關系管理器工具
contextlib模塊包含的工具用于處理背景關系管理器和with陳述句,
1.1 背景關系管理器API
背景關系管理器(context manager)負責管理一個代碼塊中的資源,會在進入代碼塊時創建資源,然后在退出代碼塊后清理這個資源,例如,檔案就支持背景關系管理器API,可以確保完成檔案讀寫后關閉檔案,
with open('test.txt', 'wt') as f: f.write('contents go here')
背景關系管理器由with陳述句啟用,這個API包括兩個方法,執行流進入with中的代碼塊時會運行__enter__()方法,它會回傳在這個背景關系中使用的一個物件,執行流離開with塊時,則掉喲這個背景關系管理器的__exit__()方法來清理所使用的資源,
class Context: def __init__(self): print('__init__()') def __enter__(self): print('__enter__()') return self def __exit__(self, exc_type, exc_val, exc_tb): print('__exit__()') with Context(): print('Doing work in the context')
相對于try:finally塊,結合背景關系管理器和with陳述句是一種更緊湊的寫法,因為總會呼叫背景關系管理器的__exit__()方法,即使產生例外的情況下也會呼叫這個方法,

如果with陳述句的as子句中指定了名,那么__enter__()方法可以回傳與這個名關聯的任何物件,在這個例子中,Context會回傳一個使用打開的背景關系的物件,
class WithinContext: def __init__(self, context): print('WithinContext.__init__({})'.format(context)) def do_something(self): print('WithinContext.do_something()') def __del__(self): print('WithinContext.__del__') class Context: def __init__(self): print('Context.__init__()') def __enter__(self): print('Context.__enter__()') return WithinContext(self) def __exit__(self, exc_type, exc_val, exc_tb): print('Context.__exit__()') with Context() as c: c.do_something()
與變數c關聯的值是__enter__()回傳的物件,這不一定是with陳述句中創建的Context實體,

__exit__()方法接收一些引數,其中包含with塊中產生的所有例外的詳細資訊,
class Context: def __init__(self, handle_error): print('__init__({})'.format(handle_error)) self.handle_error = handle_error def __enter__(self): print('__enter__()') return self def __exit__(self, exc_type, exc_val, exc_tb): print('__exit__()') print(' exc_type =', exc_type) print(' exc_val =', exc_val) print(' exc_tb =', exc_tb) return self.handle_error with Context(True): raise RuntimeError('error message handled') print() with Context(False): raise RuntimeError('error message propagated')
如果背景關系管理器可以處理這個例外,那么__exit__()應當回傳一個true值來指示這個例外不需要傳播,如果回傳false,則會在__exit__()回傳后再次拋出這個例外,

1.2 背景關系管理器作為函式修飾符
類ContextDecorator增加了對常規背景關系管理器類的支持,因此其不僅可以作為背景關系管理器,也可以作為函式修飾符,
import contextlib class Context(contextlib.ContextDecorator): def __init__(self, how_used): self.how_used = how_used print('__init__({})'.format(how_used)) def __enter__(self): print('__enter__({})'.format(self.how_used)) return self def __exit__(self, exc_type, exc_val, exc_tb): print('__exit__({})'.format(self.how_used)) @Context('as decorator') def func(message): print(message) print() with Context('as context manager'): print('Doing work in the context') print() func('Doing work in the wrapped function')
使用背景關系管理器作為修飾符時有一點不同:__enter__()回傳的值在被修飾的函式中不可用,這與使用with和as時不一樣,傳入被修飾函式的引數可以正常使用,

1.3 從生成器到背景關系管理器
采用傳統方式創建背景關系管理器并不難,即撰寫一個包含__enter__()和__exit__()方法的類,不過有些時候,如果只有很少的背景關系要管理,那么完整的寫出所有代碼便會成為額外的負擔,在這些情況下,可以使用contextmanager()修飾符將一個生成器函式轉換為背景關系管理器,
import contextlib @contextlib.contextmanager def make_context(): print(' entering') try: yield {} except RuntimeError as err: print(' ERROR:', err) finally: print(' exiting') print('Normal:') with make_context() as value: print(' inside with statement:', value) print('\nHandled error:') with make_context() as value: raise RuntimeError('showing example of handling an error') print('\nUnhandled error:') with make_context() as value: raise ValueError('this exception is not handled')
生成器要初始化背景關系,呼叫一次yield,然后清理背景關系,所生成的值(如果有)會系結到with陳述句as子句中的變數,with塊中拋出的例外會在生成器中再次拋出,從而可以在生成器中得到處理,

contextmanager()回傳的背景關系管理器派生自ContextDecorator,所以也可以被用作函式修飾符,
import contextlib @contextlib.contextmanager def make_context(): print(' entering') try: # Yield control, but not a value, because any value # yielded is not available when the context manager # is used as a decorator. yield except RuntimeError as err: print(' ERROR:', err) finally: print(' exiting') @make_context() def normal(): print(' inside with statement') @make_context() def throw_error(err): raise err print('Normal:') normal() print('\nHandled error:') throw_error(RuntimeError('showing example of handling an error')) print('\nUnhandled error:') throw_error(ValueError('this exception is not handled'))
與前面的ContextDecorator例子一樣,背景關系管理器被用作修飾符時,生成器生成的值在被修飾的函式中不可用,傳入被修飾函式的引數仍然可用,如這個例子中的throw_error()所示,

1.4 關閉打開的句柄
file類直接支持背景關系管理器API,但另外一些表示打開句柄的物件卻并不支持,contextlib的標準庫檔案中給出的示例是從urllib.urlopen()回傳的物件,另外一些遺留的類會使用close()方法但不支持背景關系管理器API,為了確保關閉句柄,要使用closing()為它創建一個背景關系管理器,
import contextlib class Door: def __init__(self): print(' __init__()') self.status = 'open' def close(self): print(' close()') self.status = 'closed' print('Normal Example:') with contextlib.closing(Door()) as door: print(' inside with statement: {}'.format(door.status)) print(' outside with statement: {}'.format(door.status)) print('\nError handling example:') try: with contextlib.closing(Door()) as door: print(' raising from inside with statement') raise RuntimeError('error message') except Exception as err: print(' Had an error:', err)
不論with塊中是否有錯誤,都會關閉這個句柄,

1.5 忽略例外
很多情況下,忽略庫產生的例外通常很有用,因為這個錯誤可能會顯示期望的狀態已經被實作,否則該錯誤可以被忽略,要忽略例外,最常見的方法是利用一個try:except陳述句,其在except塊中只包含一個pass陳述句,
class NonFatalError(Exception): pass def non_idempotent_operation(): raise NonFatalError( 'The operation failed because of existing state' ) try: print('trying non-idempotent operation') non_idempotent_operation() print('succeeded!') except NonFatalError: pass print('done')
在這種情況下,這個操作會失敗,而錯誤將被忽略,

try:except也可以被替換為contextlib.suppress(),以更顯式的抑制with塊中產生某一類例外,
import contextlib class NonFatalError(Exception): pass def non_idempotent_operation(): raise NonFatalError( 'The operation failed because of existing state' ) with contextlib.suppress(NonFatalError): print('trying non-idempotent operation') non_idempotent_operation() print('succeeded!') print('done')
在這個更新后的版本中,例外會被完全丟棄,

1.6 重定向輸出流
設計不當的庫代碼可能會直接些sys.stdout或sys.stderr,而沒有提供引數來配置不同的輸出目標,可以用redirect_stdout()和redirect_stderr()背景關系管理器從這些函式捕捉輸出,因為無法修改這些函式的源代碼來接收新的輸出引數,
from contextlib import redirect_stdout, redirect_stderr import io import sys def misbehaving_function(a): sys.stdout.write('(stdout) A: {!r}\n'.format(a)) sys.stderr.write('(stderr) A: {!r}\n'.format(a)) capture = io.StringIO() with redirect_stdout(capture), redirect_stderr(capture): misbehaving_function(5) print(capture.getvalue())
在這個例子中,misbehaving_function()同時寫至stdout和stderr,不過兩個背景關系管理器將這個輸出發送到同一個io.StringIO實體,會在這里保存以備以后使用,

1.7 動態背景關系管理器堆疊
大多數背景關系管理器都一次處理一個物件,如單個檔案或資料庫句柄,在這些情況下,物件是提前已知的,并且使用背景關系管理器的代碼可以建立這一個物件上,另外一些情況下,程式可能需要在一個背景關系中常簡未知數目的物件,控制流退出這個背景關系時所有這些物件都要清理,ExitStack就是用來處理這些更動態的情況,
ExitStack實體會維護清理回呼的一個堆疊資料結構,這些回呼顯式的填充在背景關系中,在控制流退出背景關系時會以逆序呼叫所有注冊的回呼,結果類似于有多個嵌套的with陳述句,只不過它們是動態建立的,
可以使用多種方法填充ExitStack,下面這個例子使用enter_context()來為堆疊增加一個新的背景關系管理器,
import contextlib @contextlib.contextmanager def make_context(i): print('{} entering'.format(i)) yield {} print('{} exiting'.format(i)) def variable_stack(n, msg): with contextlib.ExitStack() as stack: for i in range(n): stack.enter_context(make_context(i)) print(msg) variable_stack(2, 'inside context')
enter_context()首先在背景關系管理器上呼叫__enter__(),然后把它的__exit__()方法注冊為一個回呼,撤銷堆疊時將呼叫這個回呼,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/187341.html
標籤:Python
上一篇:18.python高階函式
