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()
无论发生什么,db1
和db2
都会运行__exit__
函数,并清理连接,刷新等。
当我把所有这些都放在课堂上时,我该怎么做? 这是对的吗?这显然不对,db1
和db2
的上下文管理器在块的末尾运行,如评论中所指出的那样。
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
编辑:修正了代码。
答案 0 :(得分:5)
我会使用第二种解决方案,但也处理数据库错误:
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__(self, type, value, traceback)
finally:
self.db2.__exit__(self, type, value, traceback)
由于__exit__
,第一个调用__enter__
中的with
- 所以,不起作用。
编辑:同时查看answer by @Ming。在许多情况下,它更短。
答案 1 :(得分:2)
取决于您要实现的整体目标。一种可能性是构建单独的上下文管理器,然后将它们与标准库contextlib.nested结合起来。这将为您提供一个单独的对象,其行为类似于您的示例MyApp
,但在DRY(不要重复自己)方式中利用现有的标准库。
答案 2 :(得分:2)
大多数上下文管理器都可以使用@contextmanager
装饰器编写。在yield是'enter'函数之前和yield之后是'exit'函数,你写一个函数有一个yield。由于如果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