contextlib — Context Manager Utilities
contextlib - 上下文管理器套件
Purpose: Utilities for creating and working with context managers.
The contextlib module contains utilities for working with context managers and the with statement.
目的: 用于創(chuàng)建和使用上下文管理器的套件滓彰。contextlib模塊包含用于使用上下文管理器即with語句的套件。
Context Manager API
上下文管理器API
A context manager is responsible for a resource within a code block, possibly creating it when the block is entered and then cleaning it up after the block is exited. For example, files support the context manager API to make it easy to ensure they are closed after all reading or writing is done.
上下文管理器主要用于處理一個代碼塊中的資源:可能在進入代碼塊時創(chuàng)建州袒,然后在代碼塊執(zhí)行結(jié)束后清理揭绑。例如:如下所示的代碼,使用支持上下文管理器API的文件打開方式將很輕易的確保在讀寫完成之后郎哭,文件都將被關(guān)閉他匪。
contextlib_file.py
with open('/tmp/pymotw.txt', 'wt') as f:
f.write('contents go here')
file is automatically closed
文件自動被關(guān)閉
A context manager is enabled by the with statement, and the API involves two methods. The enter() method is run when execution flow enters the code block inside the with. It returns an object to be used within the context. When execution flow leaves the with block, the exit() method of the context manager is called to clean up any resources being used.
上下文管理器可以由with語句開啟,他的API包含兩個方法:當程序進入with語句塊時夸研,就運行 enter()方法邦蜜。所返回的對象可以在上下文中使用。當執(zhí)行語句流要離開with語句塊時亥至,調(diào)用上下文管理器的 exit()方法將清理所使用的全部資源畦徘。
contextlib_api.py
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')
Combining a context manager and the with statement is a more compact way of writing a try:finally block, since the context manager’s exit() method is always called, even if an exception is raised.
將上下文管理器和with語句結(jié)合起來使用,比編寫try:finally語句塊顯得更簡潔抬闯,這是因為上下文管理器的 exit()方法會自動調(diào)用井辆,甚至在出現(xiàn)異常的情況下。
- python3 contextlib_api.py
__init__()
__enter__()
Doing work in the context
__exit__()
The enter() method can return any object to be associated with a name specified in the as clause of the with statement. In this example, the Context returns an object that uses the open context.
enter()方法能夠返回任何在with語句中的as子句所指定名稱相關(guān)聯(lián)的對象溶握。 在本示例中杯缺,Context類返回一個使用打開上下文的對象。
contextlib_api_other_object.py
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()
The value associated with the variable c is the object returned by enter(), which is not necessarily the Context instance created in the with statement.
與變量c相關(guān)聯(lián)的值是由enter()所返回的對象睡榆,并不是必須在with語句中創(chuàng)建的Context實例萍肆。
$ python3 contextlib_api_other_object.py
Context.init()
Context.enter()
WithinContext.init(<main.Context object at 0x1007b1c50>)
WithinContext.do_something()
Context.exit()
WithinContext.del
The exit() method receives arguments containing details of any exception raised in the with block.
exit()方法收到的參數(shù),包含with語句塊中所拋出的任何異常信息的細節(jié)胀屿。
contextlib_api_error.py
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')
If the context manager can handle the exception, exit() should return a true value to indicate that the exception does not need to be propagated. Returning false causes the exception to be re-raised after exit() returns.
如果上下文管理器能夠處理異常塘揣,exit()將返回一個true,指明異常不需要被傳播宿崭。如果返回false將導(dǎo)致異常在 exit()在返回之后再次拋出亲铡。
$ python3 contextlib_api_error.py
init(True)
enter()
exit()
exc_type = <class 'RuntimeError'>
exc_val = error message handled
exc_tb = <traceback object at 0x10115cc88>
init(False)
enter()
exit()
exc_type = <class 'RuntimeError'>
exc_val = error message propagated
exc_tb = <traceback object at 0x10115cc88>
Traceback (most recent call last):
File "contextlib_api_error.py", line 33, in <module>
raise RuntimeError('error message propagated')
RuntimeError: error message propagated
Context Managers as Function Decorators
上下文管理器作為函數(shù)裝飾器
The class ContextDecorator adds support to regular context manager classes to let them be used as function decorators as well as context managers.
ContextDecorator類增加了對于一般的上下文管理器類的支持,允許它們既作為函數(shù)裝飾器葡兑,又作為上下文管理器使用奖蔓。
contextlib_decorator.py
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')
One difference with using the context manager as a decorator is that the value returned by enter() is not available inside the function being decorated, unlike when using with and as. Arguments passed to the decorated function are available in the usual way.
使用上下文管理器作為裝飾器的一個區(qū)別在于,通過 enter()返回的值并不存在于被裝飾的函數(shù)之中讹堤,這與使用with和as不同吆鹤。傳入裝飾函數(shù)的參數(shù)與往常一樣存在。
$ python3 contextlib_decorator.py
init(as decorator)
init(as context manager)
enter(as context manager)
Doing work in the context
exit(as context manager)
enter(as decorator)
Doing work in the wrapped function
exit(as decorator)
From Generator to Context Manager
Creating context managers the traditional way, by writing a class with enter() and exit() methods, is not difficult. But sometimes writing everything out fully is extra overhead for a trivial bit of context. In those sorts of situations, use the contextmanager() decorator to convert a generator function into a context manager.
創(chuàng)建上下文管理器的傳統(tǒng)方法洲守,就是通過在類中編寫 enter() 和 exit()疑务,這并不困難沾凄。但是有時為了一點并不重要的上下文管理,就編寫全部完整的內(nèi)容知允,顯得毫無必要搭独。在這類情況下,使用contextmanager()裝飾器廊镜,將一個生成器函數(shù)轉(zhuǎn)換為一個上下文管理器函數(shù)牙肝。
contextlib_contextmanager.py
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')
The generator should initialize the context, yield exactly one time, then clean up the context. The value yielded, if any, is bound to the variable in the as clause of the with statement. Exceptions from within the with block are re-raised inside the generator, so they can be handled there.
生成器函數(shù)將初始化上下文管理器函數(shù),僅僅使用yield一次嗤朴,然后清理上下文配椭。如果有任何被yield的值,它們都被限定與with語句的as子句的變量之中雹姊。來自于with語句塊的異常會在生成器內(nèi)部重新拋出股缸,因此,它們可以在此被處理吱雏。
$ python3 contextlib_contextmanager.py
Normal:
entering
inside with statement: {}
exiting
Handled error:
entering
ERROR: showing example of handling an error
exiting
Unhandled error:
entering
exiting
Traceback (most recent call last):
File "contextlib_contextmanager.py", line 32, in <module>
raise ValueError('this exception is not handled')
ValueError: this exception is not handled
The context manager returned by contextmanager() is derived from ContextDecorator, so it also works as a function decorator.
由contextmanager()返回的上下文管理器是由ContextDecorator衍生出的敦姻,因此它以函數(shù)裝飾器的方式工作。
contextlib_contextmanager_decorator.py
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'))
As in the ContextDecorator example above, when the context manager is used as a decorator the value yielded by the generator is not available inside the function being decorated. Arguments passed to the decorated function are still available, as demonstrated by throw_error() in this example.
正如以上ContextDecorator示例所展示歧杏,當context管理器當作裝飾器使用時镰惦,通過生成器yield的值并不存在于被裝飾的函數(shù)之中。傳遞至裝飾器函數(shù)的參數(shù)仍然有效犬绒,正如在本示例中通過throw_error()所展示的旺入。
$ python3 contextlib_contextmanager_decorator.py
Normal:
entering
inside with statement
exiting
Handled error:
entering
ERROR: showing example of handling an error
exiting
Unhandled error:
entering
exiting
Traceback (most recent call last):
File "contextlib_contextmanager_decorator.py", line 43, in
<module>
throw_error(ValueError('this exception is not handled'))
File ".../lib/python3.5/contextlib.py", line 30, in inner
return func(\*args, \*\*kwds)
File "contextlib_contextmanager_decorator.py", line 33, in
throw_error
raise err
ValueError: this exception is not handled
Closing Open Handles
關(guān)閉打開操作
The file class supports the context manager API directly, but some other objects that represent open handles do not. The example given in the standard library documentation for contextlib is the object returned from urllib.urlopen(). There are other legacy classes that use a close() method but do not support the context manager API. To ensure that a handle is closed, use closing() to create a context manager for it.
file類能夠直接支持上下文管理器,但是一些表示打開文件操作的其他對象并不支持凯力。本示例由標準庫contextlib的文檔所提供茵瘾,是urllib.urlopen()所返回的對象。還有其他的遺留代碼的類咐鹤,使用close()方法拗秘,但是并不支持上下文管理器API。想要確保句柄是關(guān)閉的祈惶,為其使用closing()來創(chuàng)建一個上下文管理器雕旨。
contextlib_closing.py
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)
The handle is closed whether there is an error in the with block or not.
句柄都將被關(guān)閉,無論是否在with語句中包含錯誤行瑞。
$ python3 contextlib_closing.py
Normal Example:
init()
inside with statement: open
close()
outside with statement: closed
Error handling example:
init()
raising from inside with statement
close()
Had an error: error message
Ignoring Exceptions
It is frequently useful to ignore exceptions raised by libraries, because the error indicates that the desired state has already been achieved, or it can otherwise be ignored. The most common way to ignore exceptions is with a try:except statement with only a pass statement in the except block.
通常情況下忽略由庫函數(shù)拋出的異常信息是很有必要的奸腺,因為這些錯誤表明了所期望的狀態(tài)已經(jīng)達到餐禁,或者某種情況下可以被忽略血久。忽略異常最常用的方法就是使用try:except語句,而且在except語句塊中只放入一個pass語句帮非。
contextlib_ignore_error.py
import contextlib
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')
In this case, the operation fails and the error is ignored.
在這種情況下氧吐,操作失敗讹蘑,并且忽略錯誤。
$ python3 contextlib_ignore_error.py
trying non-idempotent operation
done
The try:except form can be replaced with contextlib.suppress() to more explicitly suppress a class of exceptions happening anywhere in the with block.
try:except形式可以使用contextlib.suppress()替換筑舅,這樣就可以更顯式的抑制語句塊中出現(xiàn)在任何位置的異常座慰。
contextlib_suppress.py
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')
In this updated version, the exception is discarded entirely.
在這個更新的版本中,異常已經(jīng)完全被拋棄了翠拣。
$ python3 contextlib_suppress.py
trying non-idempotent operation
done
Redirecting Output Streams
重定向輸出流
Poorly designed library code may write directly to sys.stdout or sys.stderr, without providing arguments to configure different output destinations. The redirect_stdout() and redirect_stderr() context managers can be used to capture output from functions like this, for which the source cannot be changed to accept a new output argument.
設(shè)計不好的庫代碼可能回直接將輸出寫入sys.stdout或者sys.stderr版仔,而沒有提供參數(shù)來配置不同的輸出方式。redirect_stdout()和redirect_stderr()上下文管理器能夠被用于捕獲類似于這樣的函數(shù)輸出:輸入源無法被改變來接受一個新的輸出參數(shù)误墓。
contextlib_redirect.py
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())
In this example, misbehaving_function() writes to both stdout and stderr, but the two context managers send that output to the same io.StringIO instance where it is saved to be used later.
在本示例中蛮粮,misbehaving_function()同時寫入stdout和stderr,但是兩個上下文管理器將輸出發(fā)送至同一個io.StringIO實例谜慌,保存以備后續(xù)使用然想。
$ python3 contextlib_redirect.py
(stdout) A: 5
(stderr) A: 5
Note
注意
Both redirect_stdout() and redirect_stderr() modify global state by replacing objects in the sys module, and should be used with care. The functions are not thread-safe, and may interfere with other operations that expect the standard output streams to be attached to terminal devices.
redirect_stdout()和redirect_stderr()函數(shù)通過覆蓋sys模塊中的對象來修改全局對象,使用時一定要很小心欣范。而且這個函數(shù)不是線程安全变泄,而且可能影響其他期望將標準輸出流附加到終端的操作。
Dynamic Context Manager Stacks
動態(tài)上下文管理器棧
Most context managers operate on one object at a time, such as a single file or database handle. In these cases, the object is known in advance and the code using the context manager can be built around that one object. In other cases, a program may need to create an unknown number of objects in a context, while wanting all of them to be cleaned up when control flow exits the context. ExitStack was created to handle these more dynamic cases.
大多數(shù)上下文管理器一次操作一個對象恼琼,例如一個文件或者一個數(shù)據(jù)庫句柄妨蛹。在這些情況下,對象是預(yù)先知道的晴竞,而且使用上下文管理器的代碼可以被用于基于一個對象構(gòu)建滑燃;在其他情況下,程序可能需要在上下文中創(chuàng)建一定未知數(shù)量的對象颓鲜,當程序控制流退出上下文時表窘,就將所有的這些對象清理。創(chuàng)建ExitStack處理這些動態(tài)類型。
An ExitStack instance maintains a stack data structure of cleanup callbacks. The callbacks are populated explicitly within the context, and any registered callbacks are called in the reverse order when control flow exits the context. The result is like having multple nested with statements, except they are established dynamically.
ExitStack示例維護了一個清理回調(diào)函數(shù)當數(shù)據(jù)結(jié)構(gòu)棧政溃〉纷鳎回調(diào)函數(shù)顯式的注入上下文,然后當控制流退出上下文時昂验,所有已注冊的回調(diào)函數(shù)依次按照逆序調(diào)用。結(jié)果就像是有多個嵌入的語句艾扮,除非他們是自動創(chuàng)建既琴。
Stacking Context Managers
上下文管理器棧
There are several ways to populate the ExitStack. This example uses enter_context() to add a new context manager to the stack.
由很多種方法為ExitStack填入數(shù)據(jù),本示例使用enter_context()方法為棧添加一個新的上下文管理器泡嘴。
contextlib_exitstack_enter_context.py
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() first calls enter() on the context manager, and then registers its exit() method as a callback to be invoked as the stack is undone.
enter_context()首先在上下文管理器上調(diào)用 enter()甫恩,然后將其注冊到 exit() 方法上,作為一旦棧被撤銷酌予,就調(diào)用的回調(diào)函數(shù)磺箕。
$ python3 contextlib_exitstack_enter_context.py
0 entering
1 entering
inside context
1 exiting
0 exiting
The context managers given to ExitStack are treated as though they are in a series of nested with statements. Errors that happen anywhere within the context propagate through the normal error handling of the context managers. These context manager classes illustrate the way errors propagate.
提供給ExitStack的上下文管理器奖慌,嵌套在一系列的with語句之中。其中上下文中任何位置發(fā)生的錯誤松靡,都將通過正常錯誤處理的上下文管理器傳播简僧。這些上下文管理器類展示了傳播錯誤的方式。
contextlib_context_managers.py
import contextlib
class Tracker:
"Base class for noisy context managers."
def __init__(self, i):
self.i = i
def msg(self, s):
print(' {}({}): {}'.format(
self.__class__.__name__, self.i, s))
def __enter__(self):
self.msg('entering')
class HandleError(Tracker):
"If an exception is received, treat it as handled."
def __exit__(self, \*exc_details):
received_exc = exc_details[1] is not None
if received_exc:
self.msg('handling exception {!r}'.format(
exc_details[1]))
self.msg('exiting {}'.format(received_exc))
# Return Boolean value indicating whether the exception
# was handled.
return received_exc
class PassError(Tracker):
"If an exception is received, propagate it."
def __exit__(self, \*exc_details):
received_exc = exc_details[1] is not None
if received_exc:
self.msg('passing exception {!r}'.format(
exc_details[1]))
self.msg('exiting')
# Return False, indicating any exception was not handled.
return False
class ErrorOnExit(Tracker):
"Cause an exception."
def __exit__(self, \*exc_details):
self.msg('throwing error')
raise RuntimeError('from {}'.format(self.i))
class ErrorOnEnter(Tracker):
"Cause an exception."
def __enter__(self):
self.msg('throwing error on enter')
raise RuntimeError('from {}'.format(self.i))
def __exit__(self, \*exc_info):
self.msg('exiting')
The examples using these classes are based around variable_stack(), which uses the context managers passed to construct an ExitStack, building up the overall context one by one. The examples below pass different context managers to explore the error handling behavior. First, the normal case of no exceptions.
本示例使用這些類都是基于variable_stack()方法雕欺,該方法使用傳入上下文管理器構(gòu)建一個ExitStack岛马,逐步構(gòu)建完整的上下文。以下示例傳遞不同的上下文管理器屠列,來測試錯誤處理行為蛛枚。首先傳入沒有異常的正常情況:
print('No errors:')
variable_stack([
HandleError(1),
PassError(2),
])
Then, an example of handling exceptions within the context managers at the end of the stack, in which all of the open contexts are closed as the stack is unwound.
然后,使用上下文管理器處理棧末尾異常的示例脸哀,在該棧中蹦浦,一旦棧展開,所有打開的上下文管理器都關(guān)閉撞蜂。
print('\nError at the end of the context stack:')
variable_stack([
HandleError(1),
HandleError(2),
ErrorOnExit(3),
])
Next, an example of handling exceptions within the context managers in the middle of the stack, in which the error does not occur until some contexts are already closed, so those contexts do not see the error.
下一步盲镶,使用上下文管理器處理棧中間異常的示例,在該棧中蝌诡,直到上下文管理器已經(jīng)關(guān)閉溉贿,棧中的錯誤都不會產(chǎn)生,因此浦旱,上下文管理器將看不到這個錯誤宇色。
print('\nError in the middle of the context stack:')
variable_stack([
HandleError(1),
PassError(2),
ErrorOnExit(3),
HandleError(4),
])
Finally, an example of the exception remaining unhandled and propagating up to the calling code.
最后,遺留的異常代碼未處理颁湖,并且傳播到調(diào)用代碼宣蠕。
try:
print('\nError ignored:')
variable_stack([
PassError(1),
ErrorOnExit(2),
])
except RuntimeError:
print('error handled outside of context')
If any context manager in the stack receives an exception and returns a True value, it prevents that exception from propagating up to any other context managers.
如果棧中的任何上下文管理器收到異常并且返回True,就阻止了向其他上下文管理器傳播異常甥捺。
$ python3 contextlib_exitstack_enter_context_errors.py
No errors:
HandleError(1): entering
PassError(2): entering
PassError(2): exiting
HandleError(1): exiting False
outside of stack, any errors were handled
Error at the end of the context stack:
HandleError(1): entering
HandleError(2): entering
ErrorOnExit(3): entering
ErrorOnExit(3): throwing error
HandleError(2): handling exception RuntimeError('from 3',)
HandleError(2): exiting True
HandleError(1): exiting False
outside of stack, any errors were handled
Error in the middle of the context stack:
HandleError(1): entering
PassError(2): entering
ErrorOnExit(3): entering
HandleError(4): entering
HandleError(4): exiting False
ErrorOnExit(3): throwing error
PassError(2): passing exception RuntimeError('from 3',)
PassError(2): exiting
HandleError(1): handling exception RuntimeError('from 3',)
HandleError(1): exiting True
outside of stack, any errors were handled
Error ignored:
PassError(1): entering
ErrorOnExit(2): entering
ErrorOnExit(2): throwing error
PassError(1): passing exception RuntimeError('from 2',)
PassError(1): exiting
error handled outside of context
Arbitrary Context Callbacks
ExitStack also supports arbitrary callbacks for closing a context, making it easy to clean up resources that are not controlled via a context manager.
為了關(guān)閉一個上下文管理器抢蚀,ExitStack 也支持任何形式的回調(diào),使不被上下文管理器控制的資源清理變的簡單镰禾。
contextlib_exitstack_callbacks.py
import contextlib
def callback(\*args, \*\*kwds):
print('closing callback({}, {})'.format(args, kwds))
with contextlib.ExitStack() as stack:
stack.callback(callback, 'arg1', 'arg2')
stack.callback(callback, arg3='val3')
Just as with the exit() methods of full context managers, the callbacks are invoked in the reverse order that they are registered.
正如完整上下文管理器的 exit() 方法皿曲,回調(diào)函數(shù)以注冊的逆序被調(diào)用。
$ python3 contextlib_exitstack_callbacks.py
closing callback((), {'arg3': 'val3'})
closing callback(('arg1', 'arg2'), {})
The callbacks are invoked regardless of whether an error occurred, and they are not given any information about whether an error occurred. Their return value is ignored.
無論是否發(fā)生錯誤吴侦,都會調(diào)用回調(diào)函數(shù)屋休,而且不會提供任何關(guān)于是否有錯誤發(fā)生的信息,直接忽略返回值备韧。
contextlib_exitstack_callbacks_error.py
import contextlib
def callback(\*args, \*\*kwds):
print('closing callback({}, {})'.format(args, kwds))
try:
with contextlib.ExitStack() as stack:
stack.callback(callback, 'arg1', 'arg2')
stack.callback(callback, arg3='val3')
raise RuntimeError('thrown error')
except RuntimeError as err:
print('ERROR: {}'.format(err))
Because they do not have access to the error, callbacks are unable to suppress exceptions from propagating through the rest of the stack of context managers.
由于并未接觸到錯誤劫樟,回調(diào)函數(shù)就不能抑制來自于通過上下文管理器其他棧傳播的異常。
$ python3 contextlib_exitstack_callbacks_error.py
closing callback((), {'arg3': 'val3'})
closing callback(('arg1', 'arg2'), {})
ERROR: thrown error
Callbacks make a convenient way to clearly define cleanup logic without the overhead of creating a new context manager class. To improve code readability, that logic can be encapsulated in an inline function, and callback() can be used as a decorator.
回調(diào)函數(shù)使定義清理函數(shù)邏輯非常清晰并且方便,而且避免了創(chuàng)建一個新的上下文管理器類的開銷毅哗。想要證明代碼的可讀性听怕,可以將邏輯封裝在一個內(nèi)嵌函數(shù)中捧挺,將回調(diào)函數(shù)作為裝飾器虑绵。
contextlib_exitstack_callbacks_decorator.py
import contextlib
with contextlib.ExitStack() as stack:
@stack.callback
def inline_cleanup():
print('inline_cleanup()')
print('local_resource = {!r}'.format(local_resource))
local_resource = 'resource created in context'
print('within the context')
There is no way to specify the arguments for functions registered using the decorator form of callback(). However, if the cleanup callback is defined inline, scope rules give it access to variables defined in the calling code.
對于使用裝飾器形式的callback()函數(shù)注冊的函數(shù)沒有辦法指定參數(shù)。然而闽烙,如果清理回調(diào)函數(shù)以內(nèi)嵌的方式定義翅睛,作用域管理將能夠給予訪問在調(diào)用代碼中定義變量的權(quán)力。
$ python3 contextlib_exitstack_callbacks_decorator.py
within the context
inline_cleanup()
local_resource = 'resource created in context'
Partial Stacks
Sometimes when building complex contexts it is useful to be able to abort an operation if the context cannot be completely constructed, but to delay the cleanup of all resources until a later time if they can all be set up properly. For example, if an operation needs several long-lived network connections, it may be best to not start the operation if one connection fails. However, if all of the connections can be opened they need to stay open longer than the duration of a single context manager. The pop_all() method of ExitStack can be used in this scenario.
有時候黑竞,當構(gòu)建復(fù)雜上下文時捕发,如果上下文沒有完整的構(gòu)建,退出機制就非常有用很魂。但是如果能夠全部恰當?shù)呐渲煤迷幔钡胶罄m(xù)某個時刻再清理全部的資源。例如:如果操作需要需要一些長時間的網(wǎng)絡(luò)連接遏匆,一旦某一個連接失敗法挨,就最好不要開始操作。然而幅聘,如果所有的連接都能打開凡纳,那么連接就需要的打開時間比單個上下文管理器的生命周期要長。ExitStack的pop_all()方法能夠在這樣的場景下使用帝蒿。
pop_all() clears all of the context managers and callbacks from the stack on which it is called, and returns a new stack pre-populated with those same context managers and callbacks. The close() method of the new stack can be invoked later, after the original stack is gone, to clean up the resources.
pop_all()函數(shù)清理了棧中所調(diào)用的所有上下文管理器和回調(diào)函數(shù)荐糜,然后返回一個新的棧,棧中預(yù)先填入了這些相同的上下文管理器和回調(diào)函數(shù)葛超。新棧的close()方法能夠后續(xù)被調(diào)用暴氏,初始的棧被清空后,就將回收所有的資源绣张。
contextlib_exitstack_pop_all.py
import contextlib
from contextlib_context_managers import *
def variable_stack(contexts):
with contextlib.ExitStack() as stack:
for c in contexts:
stack.enter_context(c)
# Return the close() method of a new stack as a clean-up
# function.
return stack.pop_all().close
# Explicitly return None, indicating that the ExitStack could
# not be initialized cleanly but that cleanup has already
# occurred.
return None
print('No errors:')
cleaner = variable_stack([
HandleError(1),
HandleError(2),
])
cleaner()
print('\nHandled error building context manager stack:')
try:
cleaner = variable_stack([
HandleError(1),
ErrorOnEnter(2),
])
except RuntimeError as err:
print('caught error {}'.format(err))
else:
if cleaner is not None:
cleaner()
else:
print('no cleaner returned')
print('\nUnhandled error building context manager stack:')
try:
cleaner = variable_stack([
PassError(1),
ErrorOnEnter(2),
])
except RuntimeError as err:
print('caught error {}'.format(err))
else:
if cleaner is not None:
cleaner()
else:
print('no cleaner returned')
This example uses the same context manager classes defined earlier, with the difference that ErrorOnEnter produces an error on enter() instead of exit(). Inside variable_stack(), if all of the contexts are entered without error then the close() method of a new ExitStack is returned. If a handled error occurs, variable_stack() returns None to indicate that the cleanup work is already done. And if an unhandled error occurs, the partial stack is cleaned up and the error is propagated.
本示例使用之前定義的相同的上下文管理器類偏序,使用不同的 ErrorOnEnter 生成一個在 enter() 的錯誤,而不是 exit()胖替。在variable_stack()內(nèi)部研儒,如果進入所有的上下文管理器,而且沒有錯誤独令,就返回新的ExitStack中的close()方法端朵。如果產(chǎn)生錯誤,variable_stack()就返回None燃箭,表明清理工作已經(jīng)完成冲呢。如果有如何未處理的錯誤發(fā)生,棧的一部分就被清理招狸,并且將錯誤拋出
$ python3 contextlib_exitstack_pop_all.py
No errors:
HandleError(1): entering
HandleError(2): entering
HandleError(2): exiting False
HandleError(1): exiting False
Handled error building context manager stack:
HandleError(1): entering
ErrorOnEnter(2): throwing error on enter
HandleError(1): handling exception RuntimeError('from 2',)
HandleError(1): exiting True
no cleaner returned
Unhandled error building context manager stack:
PassError(1): entering
ErrorOnEnter(2): throwing error on enter
PassError(1): passing exception RuntimeError('from 2',)
PassError(1): exiting
caught error from 2