我用谷歌搜索calling __enter__ manually
但没有运气。因此,让我们假设我有 MySQL 连接器类,它使用__enter__
和__exit__
函数(最初与with
语句一起使用)来连接/断开数据库。
让我们有一个使用其中2个连接的类(例如用于数据同步)。 注意:这不是我的现实场景,但它似乎是最简单的例子。
使这一切协同工作的最简单方法是这样的课程:
class DataSync(object):
def __init__(self):
self.master_connection = MySQLConnection(param_set_1)
self.slave_connection = MySQLConnection(param_set_2)
def __enter__(self):
self.master_connection.__enter__()
self.slave_connection.__enter__()
return self
def __exit__(self, exc_type, exc, traceback):
self.master_connection.__exit__(exc_type, exc, traceback)
self.slave_connection.__exit__(exc_type, exc, traceback)
# Some real operation functions
# Simple usage example
with DataSync() as sync:
records = sync.master_connection.fetch_records()
sync.slave_connection.push_records(records)
问:这样手动拨打__enter__
/ __exit__
可以(有什么不对)吗?
Pylint 1.1.0没有对此发出任何警告,也没有找到任何关于它的文章(在开始的谷歌链接)。
那么打电话:
try:
# Db query
except MySQL.ServerDisconnectedException:
self.master_connection.__exit__(None, None, None)
self.master_connection.__enter__()
# Retry
这是一个好/坏的做法?为什么呢?
答案 0 :(得分:12)
你的第一个例子不是一个好主意:
如果slave_connection.__enter__
引发异常,会发生什么:
master_connection
获取其资源slave_connection
失败DataSync.__enter__
宣传异常DataSync.__exit__
无法运行master_connection
永远不会被清理干净!如果master_connection.__exit__
引发异常会怎样?
DataSync.__exit__
提前结束slave_connection
永远不会被清理干净! contextlib.ExitStack
可以在这里提供帮助:
def __enter__(self):
with ExitStack() as stack:
stack.enter_context(self.master_connection)
stack.enter_context(self.slave_connection)
self._stack = stack.pop_all()
return self
def __exit__(self, exc_type, exc, traceback):
self._stack.__exit__(self, exc_type, exc, traceback)
提出同样的问题:
如果slave_connection.__enter__
引发异常,会发生什么:
stack
清除master_connection
如果master_connection.__exit__
引发异常会怎样?
slave_connection
在调用之前被清理好的,如果slave_connection.__exit__
抛出异常会怎样?
ExitStack
确保调用master_connection.__exit__
从属连接发生的任何事情直接调用__enter__
没有错,但是如果你需要在多个对象上调用它,请确保正确清理!
答案 1 :(得分:11)
不,这没什么不对。标准库中甚至有一些地方可以执行此操作。与multiprocessing
module:
class SemLock(object):
def __init__(self, kind, value, maxvalue, *, ctx):
...
try:
sl = self._semlock = _multiprocessing.SemLock(
kind, value, maxvalue, self._make_name(),
unlink_now)
except FileExistsError:
pass
...
def __enter__(self):
return self._semlock.__enter__()
def __exit__(self, *args):
return self._semlock.__exit__(*args)
class _TemporaryFileWrapper:
def __init__(self, file, name, delete=True):
self.file = file
self.name = name
self.delete = delete
self._closer = _TemporaryFileCloser(file, name, delete)
...
# The underlying __enter__ method returns the wrong object
# (self.file) so override it to return the wrapper
def __enter__(self):
self.file.__enter__()
return self
# Need to trap __exit__ as well to ensure the file gets
# deleted when used in a with statement
def __exit__(self, exc, value, tb):
result = self.file.__exit__(exc, value, tb)
self.close()
return result
标准库示例不会为两个对象调用__enter__
/ __exit__
,但是如果您有一个对象负责创建/销毁多个对象的上下文而不是一个,为所有人调用__enter__
/ __exit__
都没问题。
唯一可能的问题是正确处理您正在管理的对象的__enter__
__exit__
次调用的返回值。使用__enter__
,您需要确保返回包装器对象的用户所需的state
以从with ... as <state>:
调用返回。使用__exit__
,您需要决定是否要传播上下文中发生的任何异常(通过返回False
),或者抑制它(通过返回True
)。您的托管对象可以尝试以任何方式执行此操作,您需要确定对包装器对象有意义的内容。
答案 2 :(得分:0)
我仅回答标题,即调用__enter__
和__exit__
,例如从IPython提示。您可以这样操作:
ctx.__enter__()
ctx.__exit__(*sys.exc_info())