手动调用__enter__和__exit__

Vyk*_*tor 22 python contextmanager python-3.x

我用谷歌搜索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)
Run Code Online (Sandbox Code Playgroud)

:这样调用__enter__/ __exit__手动是否可以(有什么不对)吗?

Pylint 1.1.0没有对此发出任何警告,也没有找到任何关于它的文章(在开始的谷歌链接).

那叫什么呢:

try:
    # Db query
except MySQL.ServerDisconnectedException:
    self.master_connection.__exit__(None, None, None)
    self.master_connection.__enter__()
    # Retry
Run Code Online (Sandbox Code Playgroud)

这是一个好/坏的做法?为什么?

Eri*_*ric 14

你的第一个例子不是一个好主意:

  1. 如果slave_connection.__enter__抛出异常会发生什么:

    • master_connection 获得其资源
    • slave_connection 失败
    • DataSync.__enter__ 传播异常
    • DataSync.__exit__ 不运行
    • master_connection 从来没有清理过!
    • 坏事的可能性
  2. 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)
Run Code Online (Sandbox Code Playgroud)

问同样的问题:

  1. 如果slave_connection.__enter__抛出异常会发生什么:

    • with块退出,并stack清理master_connection
    • 一切都好!
  2. master_connection.__exit__抛出异常会发生什么?

    • 没关系,slave_connection在调用之前要清理干净
    • 一切都好!
  3. 好的,如果slave_connection.__exit__抛出异常会发生什么?

    • ExitStack确保调用master_connection.__exit__从属连接发生的任何事情
    • 一切都好!

__enter__直接调用没有错,但如果你需要在多个对象上调用它,请确保正确清理!

  • @Vyktor:对于早期版本,总有[`contextlib2`](https://pypi.python.org/pypi/contextlib2)后退端口!甚至在ExitStack之前,你可以通过一些仔细的尝试/最终实现安全 (2认同)

dan*_*ano 12

不,这没什么不对.标准库中甚至有一些地方可以执行此操作.像__enter__模块一样:

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)
Run Code Online (Sandbox Code Playgroud)

__exit__模块:

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
Run Code Online (Sandbox Code Playgroud)

标准库示例不是调用multiprocessing/ tempfile用于两个对象,但如果您有一个对象负责创建/销毁多个对象的上下文而不是一个对象,则调用__enter__/ __exit__为所有对象都可以.

唯一可能的问题是正确处理__enter__ __exit__您正在管理的对象的调用的返回值.有了__enter__,您需要确保返回__exit__包装器对象的用户所需的任何内容以从__enter__调用中返回.有了state,您需要决定是否要传播上下文中发生的任何异常(通过返回with ... as <state>:),或者抑制它(通过返回__exit__).您的托管对象可以尝试以任何方式执行此操作,您需要确定对包装器对象有意义的内容.

  • “如果您有一个对象负责创建/销毁多个对象而不是一个对象的上下文,则对所有对象调用`__enter__` /`__exit__`很好”-不,不是,至少不是没有管理一路上可能发生的故障。 (2认同)

Rol*_*ier 10

我只回答标题,即从 IPython 提示符进行__enter__调用。__exit__你可以这样做:


ctx.__enter__()
ctx.__exit__(*sys.exc_info())

Run Code Online (Sandbox Code Playgroud)