我们有代码根据运行时参数调用可变数量的上下文管理器:
from contextlib import nested, contextmanager
@contextmanager
def my_context(arg):
print("entering", arg)
try:
yield arg
finally:
print("exiting", arg)
def my_fn(items):
with nested(*(my_context(arg) for arg in items)) as managers:
print("processing under", managers)
my_fn(range(3))
Run Code Online (Sandbox Code Playgroud)
但是,contextlib.nested自Python 2.7以来已弃用:
DeprecationWarning: With-statements now directly support multiple context managers
Run Code Online (Sandbox Code Playgroud)
Python'with '语句中的多个变量的答案表明contextlib.nested存在一些"令人困惑的容易出错的怪癖",但建议使用多管理器with语句的替代方法不适用于可变数量的上下文管理器(并且还会破坏向后兼容性) ).
是否有任何替代品contextlib.nested不被弃用,并且(最好)没有相同的错误?
或者我应该继续使用contextlib.nested并忽略警告?如果是这样,我是否应该计划contextlib.nested在将来的某个时间将其删除?
据我了解,__init__()和__enter__()上下文管理的方法调用一次每个,一个接一个,不留下任何机会之间执行任何其他代码.将它们分成两种方法的目的是什么,我应该将它们放入每种方法中?
编辑:对不起,没注意文档.
编辑2:实际上,我感到困惑的原因是因为我在想@contextmanager装饰师.使用创建的上下文管理器@contextmananger只能使用一次(第一次使用后生成器将耗尽),因此通常使用构造函数内部with语句编写; 如果这是使用with陈述的唯一方法,我的问题就有意义了.当然,实际上,情境管理者比@contextmanager可以创造的更为笼统; 特别是上下文管理器通常可以重用.我希望这次能做对吗?
__exit__()即使存在异常,是否可以确保调用该方法__enter__()?
>>> class TstContx(object):
... def __enter__(self):
... raise Exception('Oops in __enter__')
...
... def __exit__(self, e_typ, e_val, trcbak):
... print "This isn't running"
...
>>> with TstContx():
... pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __enter__
Exception: Oops in __enter__
>>>
Run Code Online (Sandbox Code Playgroud)
编辑
这是我能得到的尽可能接近......
class TstContx(object):
def __enter__(self):
try:
# __enter__ code
except Exception as e
self.init_exc = e
return self
def __exit__(self, e_typ, e_val, trcbak): …Run Code Online (Sandbox Code Playgroud) 在Python 2.6中,我们使用以下方式格式化嵌套上下文管理器:
with nested(
context1,
context2
) as a, b:
pass
Run Code Online (Sandbox Code Playgroud)
从Python 2.7开始,nested不推荐使用.我在一行上看到了很多关于多个上下文管理器的例子,但我找不到允许它们在多行上的语法.你会怎么做?
# That's working fine
with context1 as a, context2 as b:
pass
# But how do we make it multine?
# These are not working
with (
context1,
context2
) as a, b:
pass
with context1 as a,
context2 as b:
pass
Run Code Online (Sandbox Code Playgroud) 对于某些任务,通常需要多个具有明确释放资源的对象 - 例如,两个文件; 当任务是使用嵌套with块的函数的本地任务时,这很容易完成,或者 - 甚至更好 - with具有多个with_item子句的单个块:
with open('in.txt', 'r') as i, open('out.txt', 'w') as o:
# do stuff
Run Code Online (Sandbox Code Playgroud)
OTOH,当这些对象不仅仅是函数作用域的本地对象,而是由类实例拥有 - 换句话说,上下文管理器如何构成时,我仍然很难理解它应该如何工作.
理想情况下,我想做的事情如下:
class Foo:
def __init__(self, in_file_name, out_file_name):
self.i = WITH(open(in_file_name, 'r'))
self.o = WITH(open(out_file_name, 'w'))
Run Code Online (Sandbox Code Playgroud)
并有Foo自己变成上下文管理,处理i和o,这样,当我做
with Foo('in.txt', 'out.txt') as f:
# do stuff
Run Code Online (Sandbox Code Playgroud)
self.i并self.o按照您的预期自动处理.
我修改了写东西,比如:
class Foo:
def __init__(self, in_file_name, out_file_name):
self.i = open(in_file_name, 'r').__enter__()
self.o = open(out_file_name, 'w').__enter__()
def __enter__(self):
return …Run Code Online (Sandbox Code Playgroud) 我用谷歌搜索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__() …Run Code Online (Sandbox Code Playgroud) 假设您正在使用multiprocessing.Pool对象,并且您正在使用initializer构造函数的设置来传递初始化函数,然后在全局命名空间中创建资源.假设资源有一个上下文管理器.您将如何处理上下文管理资源的生命周期,前提是它必须贯穿整个过程的生命周期,但最终应该进行适当的清理?
到目前为止,我有点像这样:
resource_cm = None
resource = None
def _worker_init(args):
global resource
resource_cm = open_resource(args)
resource = resource_cm.__enter__()
Run Code Online (Sandbox Code Playgroud)
从此处开始,池进程可以使用该资源.到现在为止还挺好.但处理清理有点棘手,因为multiprocessing.Pool类没有提供destructor或deinitializer参数.
我的一个想法是使用该atexit模块,并在初始化程序中注册清理.像这样的东西:
def _worker_init(args):
global resource
resource_cm = open_resource(args)
resource = resource_cm.__enter__()
def _clean_up():
resource_cm.__exit__()
import atexit
atexit.register(_clean_up)
Run Code Online (Sandbox Code Playgroud)
这是一个好方法吗?有更简单的方法吗?
编辑:atexit似乎没有工作.至少不是我上面使用它的方式,所以到目前为止我还没有解决这个问题的方法.
我知道这已被广泛讨论,但我仍然找不到答案来证实这一点:with语句与在try - (除了)-finally块中调用相同代码相同,其中无论在__exit__函数中定义了什么上下文管理器放在finally块中?
例如 - 这两个代码片段完全相同吗?
import sys
from contextlib import contextmanager
@contextmanager
def open_input(fpath):
fd = open(fpath) if fpath else sys.stdin
try:
yield fd
finally:
fd.close()
with open_input("/path/to/file"):
print "starting to read from file..."
Run Code Online (Sandbox Code Playgroud)
同样如下:
def open_input(fpath):
try:
fd = open(fpath) if fpath else sys.stdin
print "starting to read from file..."
finally:
fd.close()
open_input("/path/to/file")
Run Code Online (Sandbox Code Playgroud)
谢谢!
MySQLdb Connections有一个基本的上下文管理器,可以在enter上创建一个游标,在退出时回退或提交,并且隐式不会抑制异常.来自连接源:
def __enter__(self):
if self.get_autocommit():
self.query("BEGIN")
return self.cursor()
def __exit__(self, exc, value, tb):
if exc:
self.rollback()
else:
self.commit()
Run Code Online (Sandbox Code Playgroud)
那么,有没有人知道为什么光标在退出时没有关闭?
起初,我认为这是因为关闭游标没有做任何事情,并且游标只有一个关闭方法来参考Python DB API(参见本答案的评论).但是,事实是关闭光标会烧掉剩余的结果集(如果有),并禁用光标.从光标源:
def close(self):
"""Close the cursor. No further queries will be possible."""
if not self.connection: return
while self.nextset(): pass
self.connection = None
Run Code Online (Sandbox Code Playgroud)
在退出处关闭光标会很容易,所以我不得不假设它没有故意完成.另一方面,我们可以看到,当一个游标被删除时,无论如何它都会被关闭,所以我猜垃圾收集器最终会绕过它.我对Python中的垃圾收集知之甚少.
def __del__(self):
self.close()
self.errorhandler = None
self._result = None
Run Code Online (Sandbox Code Playgroud)
另一个猜测是,您可能希望在with块之后重新使用光标.但我想不出你为什么需要这样做的任何理由.难道你不能总是在其上下文中使用游标,并且只为下一个事务使用单独的上下文吗?
要非常清楚,这个例子显然没有意义:
with conn as cursor:
cursor.execute(select_stmt)
rows = …Run Code Online (Sandbox Code Playgroud) 我使用Python 2.7,我知道我可以这样写:
with A() as a, B() as b:
do_something()
Run Code Online (Sandbox Code Playgroud)
我想提供一个方便的助手,两者都做.此助手的用法应如下所示:
with AB() as ab:
do_something()
Run Code Online (Sandbox Code Playgroud)
现在AB()应该做两件事:创建上下文A()并创建上下文B().
我不知道如何编写这个便利助手
contextmanager ×10
python ×10
python-3.x ×2
deprecated ×1
exception ×1
mysql ×1
mysql-python ×1
python-2.7 ×1