继承上下文管理器的Python方法是什么

dvtswwa3  于 2023-03-16  发布在  Python
关注(0)|答案(3)|浏览(111)

Python教我们使用__enter____exit__来清理对象。如果我需要创建一个对象,而该对象必须使用上下文管理器,那该怎么办?想象一下:

from database1 import DB1
from database2 import DB2

通常情况下,它们的用法如下:

with DB1() as db1, DB2() as db2:
    db1.do_stuff()
    db2.do_other_stuff()

无论发生什么,db1db2都将运行它们的__exit__函数,并清理连接、刷新等。
当我把所有这些都放到一个类中时,我该怎么做呢?这是对的吗?这显然是不对的,db1db2的上下文管理器运行在块的末尾,正如注解中指出的那样。

class MyApp(object):
    def __enter__(self):
        with DB1() as self.db1, DB2() as self.db2:
            return self
    def __exit__(self, type, value, traceback):
        self.db1.__exit__(self, type, value, traceback)
        self.db2.__exit__(self, type, value, traceback)

我甚至考虑过这样做:这看起来是个好主意,实际上(经过一些清理):

class MyApp(object):
    def __init__(self):
        self.db1 = DB1()
        self.db2 = DB2()
    def __enter__(self):
        self.db1.__enter__()
        self.db2.__enter__()
        return self
    def __exit__(self, type, value, traceback):
        try:
            self.db1.__exit__(self, type, value, traceback)
        except:
            pass
        try:
            self.db2.__exit__(self, type, value, traceback)
        except:
            pass

编辑:修复代码。

wmtdaxz3

wmtdaxz31#

我会使用第二种解决方案,但也会处理数据库错误:

import sys

class MyApp(object):
    def __init__(self):
        self.db1 = DB1()
        self.db2 = DB2()
    def __enter__(self):
        self.db1.__enter__()
        try:
            self.db2.__enter__()
        except:
            self.db1.__exit__(None, None, None) # I am not sure with None
            raise
        return self
    def __exit__(self, type, value, traceback):
        try:
            self.db1.__exit__(type, value, traceback)
        finally:
            self.db2.__exit__(type, value, traceback)

第一个函数在__enter__中调用__exit__,因为with-所以,不起作用。

**编辑:**还可以查看answer by @Ming。在许多情况下,它更短。

sbtkgmzw

sbtkgmzw2#

大多数上下文管理器都可以使用@contextmanager装饰器来编写。您可以编写一个带有一个yield的函数,在yield之前是您的“enter”函数,在yield之后是您的“exit”函数。由于生成器的实现方式,如果yield在with语句中,则with语句不会在yield处退出。
例如

from contextlib import contextmanager

class SomeContextManager:
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        print("enter", self.name)
        return self
    def __exit__(self, ex_type, value, traceback):
        print("exit", self.name)

class SomeContextManagerWrapper:
    def __init__(self, *context_managers):
        self.context_managers = context_managers
    @property
    def names(self):
        return [cm.name for cm in self.context_managers]

@contextmanager
def context_manager_combiner():
    print("context_manager_combiner entering")
    with SomeContextManager("first") as a, SomeContextManager("second") as b:
        yield SomeContextManagerWrapper(a, b)
    print("context_manager_combiner exiting")

with context_manager_combiner() as wrapper:
    print("in with statement with:", wrapper.names)

产出:

context_manager_combiner entering
enter first
enter second
in with statement with: ['first', 'second']
exit second
exit first
context_manager_combiner exiting
mrphzbgm

mrphzbgm3#

这取决于你想要达到的总体目标。一种可能性是构建单独的上下文管理器,然后将它们与标准库的contextlib.nested结合起来。这将为你提供一个单一的对象,它的行为类似于你的示例MyApp,但以DRY(不要重复自己)的方式利用现有的标准库。

相关问题