Python 中的条件 with 语句
Conditional with statement in Python
有没有一种方法可以有条件地以 with 语句开始一段代码?
类似于:
if needs_with():
with get_stuff() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
澄清一下,一种情况是将一个块封装在 with 语句中,而另一种可能性是相同的块,但未封装(即,就好像它没有缩进一样)
最初的实验当然会出现缩进错误..
您可以使用 contextlib.nested
将 0 个或多个上下文管理器放入单个 with
语句中。
>>> import contextlib
>>> managers = []
>>> test_me = True
>>> if test_me:
... managers.append(open('x.txt','w'))
...
>>> with contextlib.nested(*managers):
... pass
...
>>> # see if it closed
... managers[0].write('hello')
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: I/O operation on closed file
这个解决方案有它的怪癖,我刚刚注意到从 2.7 开始它已被弃用。我编写了自己的上下文管理器来处理多个上下文管理器。到目前为止它对我有用,但我还没有真正考虑过边缘条件
class ContextGroup(object):
"""A group of context managers that all exit when the group exits."""
def __init__(self):
"""Create a context group"""
self._exits = []
def add(self, ctx_obj, name=None):
"""Open a context manager on ctx_obj and add to this group. If
name, the context manager will be available as self.name. name
will still reference the context object after this context
closes.
"""
if name and hasattr(self, name):
raise AttributeError("ContextGroup already has context %s" % name)
self._exits.append(ctx_obj.__exit__)
var = ctx_obj.__enter__()
if name:
self.__dict__[name] = var
def exit_early(self, name):
"""Call __exit__ on named context manager and remove from group"""
ctx_obj = getattr(self, name)
delattr(self, name)
del self._exits[self._exits.index(ctx_obj)]
ctx_obj.__exit__(None, None, None)
def __enter__(self):
return self
def __exit__(self, _type, value, tb):
inner_exeptions = []
for _exit in self._exits:
try:
_exit(_type, value, tb )
except Exception, e:
inner_exceptions.append(e)
if inner_exceptions:
r = RuntimeError("Errors while exiting context: %s"
% (','.join(str(e)) for e in inner_exceptions))
def __setattr__(self, name, val):
if hasattr(val, '__exit__'):
self.add(val, name)
else:
self.__dict__[name] = val
如果您想避免重复代码并使用 3.7(引入 contextlib.nullcontext
时)甚至 3.3(引入 contextlib.ExitStack
时)之前的 Python 版本,你可以这样做:
class dummy_context_mgr():
def __enter__(self):
return None
def __exit__(self, exc_type, exc_value, traceback):
return False
或:
import contextlib
@contextlib.contextmanager
def dummy_context_mgr():
yield None
然后将其用作:
with get_stuff() if needs_with() else dummy_context_mgr() as gs:
# do stuff involving gs or not
您也可以根据 needs_with()
.
制作 get_stuff()
return 不同的东西
(请参阅 or 了解您可以在更高版本中执行的操作。)
Python 3.3 引入了 contextlib.ExitStack
就是为了这种情况。它为您提供了一个 "stack",您可以根据需要向其中添加上下文管理器。在你的情况下,你会这样做:
from contextlib import ExitStack
with ExitStack() as stack:
if needs_with():
gs = stack.enter_context(get_stuff())
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
任何输入到 stack
的内容都会像往常一样在 with
语句的末尾自动 exit
ed。 (如果未输入任何内容,那不是问题。)在此示例中,get_stuff()
返回的任何内容都会自动 exit
ed。
如果您必须使用早期版本的 python,您可以使用 contextlib2
模块,尽管这不是标准的。它将此功能和其他功能反向移植到 python 的早期版本。如果您喜欢这种方法,您甚至可以进行条件导入。
实现这一目标的第三方选项:
https://pypi.python.org/pypi/conditional
from conditional import conditional
with conditional(needs_with(), get_stuff()):
# do stuff
从 Python 3.7 开始,您可以使用 contextlib.nullcontext
:
from contextlib import nullcontext
if needs_with():
cm = get_stuff()
else:
cm = nullcontext()
with cm as gs:
# Do stuff
contextlib.nullcontext
几乎只是一个空操作上下文管理器。如果你依赖于 as
:
之后存在的东西,你可以传递一个它会产生的参数
>>> with nullcontext(5) as value:
... print(value)
...
5
否则就是return None
:
>>> with nullcontext() as value:
... print(value)
...
None
它非常简洁,请在此处查看文档:https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext
很难找到@farsil 的漂亮 Python 3.3 一行,所以这里是它自己的答案:
with ExitStack() if not needs_with() else get_stuff() as gs:
# do stuff
注意ExitStack应该在前,否则get_stuff()
会被计算。
所以我做了这段代码;
它是这样调用的:
with c_with(needs_with(), lambda: get_stuff()) as gs:
##DOESN't call get_stuff() unless needs_with is called.
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
属性:
- 它不会调用
get_stuff()
除非条件为真
- 如果条件为假,它提供一个虚拟上下文管理器。 (对于 python >= 3.7,可以用
contextlib.nullcontext
代替)
- 如果条件为假,您可以选择发送另一个上下文管理器:
with c_with(needs_with(), lambda: get_stuff(), lambda: dont_get_stuff()) as gs:
希望这对某人有所帮助!
-- 代码如下:
def call_if_lambda(f):
"""
Calls f if f is a lambda function.
From
"""
LMBD = lambda:0
islambda=isinstance(f, type(LMBD)) and f.__name__ == LMBD.__name__
return f() if islambda else f
import types
class _DummyClass(object):
"""
A class that doesn't do anything when methods are called, items are set and get etc.
I suspect this does not cover _all_ cases, but many.
"""
def _returnself(self, *args, **kwargs):
return self
__getattr__=__enter__=__exit__=__call__=__getitem__=_returnself
def __str__(self):
return ""
__repr__=__str__
def __setitem__(*args,**kwargs):
pass
def __setattr__(*args,**kwargs):
pass
class c_with(object):
"""
Wrap another context manager and enter it only if condition is true.
Parameters
----------
condition: bool
Condition to enter contextmanager or possibly else_contextmanager
contextmanager: contextmanager, lambda or None
Contextmanager for entering if condition is true. A lambda function
can be given, which will not be called unless entering the contextmanager.
else_contextmanager: contextmanager, lambda or None
Contextmanager for entering if condition is true. A lambda function
can be given, which will not be called unless entering the contextmanager.
If None is given, then a dummy contextmanager is returned.
"""
def __init__(self, condition, contextmanager, else_contextmanager=None):
self.condition = condition
self.contextmanager = contextmanager
self.else_contextmanager = _DummyClass() if else_contextmanager is None else else_contextmanager
def __enter__(self):
if self.condition:
self.contextmanager=call_if_lambda(self.contextmanager)
return self.contextmanager.__enter__()
elif self.else_contextmanager is not None:
self.else_contextmanager=call_if_lambda(self.else_contextmanager)
return self.else_contextmanager.__enter__()
def __exit__(self, *args):
if self.condition:
return self.contextmanager.__exit__(*args)
elif self.else_contextmanager is not None:
self.else_contextmanager.__exit__(*args)
#### EXAMPLE BELOW ####
from contextlib import contextmanager
def needs_with():
return False
@contextmanager
def get_stuff():
yield {"hello":"world"}
with c_with(needs_with(), lambda: get_stuff()) as gs:
## DOESN't call get_stuff() unless needs_with() returns True.
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
print("Hello",gs['hello'])
我发现@Anentropic 不完整。
from conditional import conditional
a = 1 # can be None
if not a is None:
b = 1
class WithNone:
def __enter__(self):
return self
def __exit__(self, type, value, tb):
pass
def foo(x):
print(x)
return WithNone()
with conditional(not a is None, foo(b) if not a is None else None):
print(123)
完整的 conditional
用法需要 3 个条件而不是 1 个,原因是:
NameError: name 'b' is not defined
如果没有定义 a
- 函数
foo
仍然必须return可输入对象,否则:AttributeError: 'NoneType' object has no attribute '__enter__'
import contextlib
my_context = None # your context
my_condition = False # your condition
# Option 1 (Recommended)
with my_context if my_condition else contextlib.nullcontext():
print('hello 1')
# Option 2
with my_context if my_condition else contextlib.ExitStack():
print('hello 2')
有没有一种方法可以有条件地以 with 语句开始一段代码?
类似于:
if needs_with():
with get_stuff() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
澄清一下,一种情况是将一个块封装在 with 语句中,而另一种可能性是相同的块,但未封装(即,就好像它没有缩进一样)
最初的实验当然会出现缩进错误..
您可以使用 contextlib.nested
将 0 个或多个上下文管理器放入单个 with
语句中。
>>> import contextlib
>>> managers = []
>>> test_me = True
>>> if test_me:
... managers.append(open('x.txt','w'))
...
>>> with contextlib.nested(*managers):
... pass
...
>>> # see if it closed
... managers[0].write('hello')
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: I/O operation on closed file
这个解决方案有它的怪癖,我刚刚注意到从 2.7 开始它已被弃用。我编写了自己的上下文管理器来处理多个上下文管理器。到目前为止它对我有用,但我还没有真正考虑过边缘条件
class ContextGroup(object):
"""A group of context managers that all exit when the group exits."""
def __init__(self):
"""Create a context group"""
self._exits = []
def add(self, ctx_obj, name=None):
"""Open a context manager on ctx_obj and add to this group. If
name, the context manager will be available as self.name. name
will still reference the context object after this context
closes.
"""
if name and hasattr(self, name):
raise AttributeError("ContextGroup already has context %s" % name)
self._exits.append(ctx_obj.__exit__)
var = ctx_obj.__enter__()
if name:
self.__dict__[name] = var
def exit_early(self, name):
"""Call __exit__ on named context manager and remove from group"""
ctx_obj = getattr(self, name)
delattr(self, name)
del self._exits[self._exits.index(ctx_obj)]
ctx_obj.__exit__(None, None, None)
def __enter__(self):
return self
def __exit__(self, _type, value, tb):
inner_exeptions = []
for _exit in self._exits:
try:
_exit(_type, value, tb )
except Exception, e:
inner_exceptions.append(e)
if inner_exceptions:
r = RuntimeError("Errors while exiting context: %s"
% (','.join(str(e)) for e in inner_exceptions))
def __setattr__(self, name, val):
if hasattr(val, '__exit__'):
self.add(val, name)
else:
self.__dict__[name] = val
如果您想避免重复代码并使用 3.7(引入 contextlib.nullcontext
时)甚至 3.3(引入 contextlib.ExitStack
时)之前的 Python 版本,你可以这样做:
class dummy_context_mgr():
def __enter__(self):
return None
def __exit__(self, exc_type, exc_value, traceback):
return False
或:
import contextlib
@contextlib.contextmanager
def dummy_context_mgr():
yield None
然后将其用作:
with get_stuff() if needs_with() else dummy_context_mgr() as gs:
# do stuff involving gs or not
您也可以根据 needs_with()
.
get_stuff()
return 不同的东西
(请参阅
Python 3.3 引入了 contextlib.ExitStack
就是为了这种情况。它为您提供了一个 "stack",您可以根据需要向其中添加上下文管理器。在你的情况下,你会这样做:
from contextlib import ExitStack
with ExitStack() as stack:
if needs_with():
gs = stack.enter_context(get_stuff())
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
任何输入到 stack
的内容都会像往常一样在 with
语句的末尾自动 exit
ed。 (如果未输入任何内容,那不是问题。)在此示例中,get_stuff()
返回的任何内容都会自动 exit
ed。
如果您必须使用早期版本的 python,您可以使用 contextlib2
模块,尽管这不是标准的。它将此功能和其他功能反向移植到 python 的早期版本。如果您喜欢这种方法,您甚至可以进行条件导入。
实现这一目标的第三方选项:
https://pypi.python.org/pypi/conditional
from conditional import conditional
with conditional(needs_with(), get_stuff()):
# do stuff
从 Python 3.7 开始,您可以使用 contextlib.nullcontext
:
from contextlib import nullcontext
if needs_with():
cm = get_stuff()
else:
cm = nullcontext()
with cm as gs:
# Do stuff
contextlib.nullcontext
几乎只是一个空操作上下文管理器。如果你依赖于 as
:
>>> with nullcontext(5) as value:
... print(value)
...
5
否则就是return None
:
>>> with nullcontext() as value:
... print(value)
...
None
它非常简洁,请在此处查看文档:https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext
很难找到@farsil 的漂亮 Python 3.3 一行,所以这里是它自己的答案:
with ExitStack() if not needs_with() else get_stuff() as gs:
# do stuff
注意ExitStack应该在前,否则get_stuff()
会被计算。
所以我做了这段代码; 它是这样调用的:
with c_with(needs_with(), lambda: get_stuff()) as gs:
##DOESN't call get_stuff() unless needs_with is called.
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
属性:
- 它不会调用
get_stuff()
除非条件为真 - 如果条件为假,它提供一个虚拟上下文管理器。 (对于 python >= 3.7,可以用
contextlib.nullcontext
代替) - 如果条件为假,您可以选择发送另一个上下文管理器:
with c_with(needs_with(), lambda: get_stuff(), lambda: dont_get_stuff()) as gs:
希望这对某人有所帮助!
-- 代码如下:
def call_if_lambda(f):
"""
Calls f if f is a lambda function.
From
"""
LMBD = lambda:0
islambda=isinstance(f, type(LMBD)) and f.__name__ == LMBD.__name__
return f() if islambda else f
import types
class _DummyClass(object):
"""
A class that doesn't do anything when methods are called, items are set and get etc.
I suspect this does not cover _all_ cases, but many.
"""
def _returnself(self, *args, **kwargs):
return self
__getattr__=__enter__=__exit__=__call__=__getitem__=_returnself
def __str__(self):
return ""
__repr__=__str__
def __setitem__(*args,**kwargs):
pass
def __setattr__(*args,**kwargs):
pass
class c_with(object):
"""
Wrap another context manager and enter it only if condition is true.
Parameters
----------
condition: bool
Condition to enter contextmanager or possibly else_contextmanager
contextmanager: contextmanager, lambda or None
Contextmanager for entering if condition is true. A lambda function
can be given, which will not be called unless entering the contextmanager.
else_contextmanager: contextmanager, lambda or None
Contextmanager for entering if condition is true. A lambda function
can be given, which will not be called unless entering the contextmanager.
If None is given, then a dummy contextmanager is returned.
"""
def __init__(self, condition, contextmanager, else_contextmanager=None):
self.condition = condition
self.contextmanager = contextmanager
self.else_contextmanager = _DummyClass() if else_contextmanager is None else else_contextmanager
def __enter__(self):
if self.condition:
self.contextmanager=call_if_lambda(self.contextmanager)
return self.contextmanager.__enter__()
elif self.else_contextmanager is not None:
self.else_contextmanager=call_if_lambda(self.else_contextmanager)
return self.else_contextmanager.__enter__()
def __exit__(self, *args):
if self.condition:
return self.contextmanager.__exit__(*args)
elif self.else_contextmanager is not None:
self.else_contextmanager.__exit__(*args)
#### EXAMPLE BELOW ####
from contextlib import contextmanager
def needs_with():
return False
@contextmanager
def get_stuff():
yield {"hello":"world"}
with c_with(needs_with(), lambda: get_stuff()) as gs:
## DOESN't call get_stuff() unless needs_with() returns True.
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
print("Hello",gs['hello'])
我发现@Anentropic
from conditional import conditional
a = 1 # can be None
if not a is None:
b = 1
class WithNone:
def __enter__(self):
return self
def __exit__(self, type, value, tb):
pass
def foo(x):
print(x)
return WithNone()
with conditional(not a is None, foo(b) if not a is None else None):
print(123)
完整的 conditional
用法需要 3 个条件而不是 1 个,原因是:
NameError: name 'b' is not defined
如果没有定义a
- 函数
foo
仍然必须return可输入对象,否则:AttributeError: 'NoneType' object has no attribute '__enter__'
import contextlib
my_context = None # your context
my_condition = False # your condition
# Option 1 (Recommended)
with my_context if my_condition else contextlib.nullcontext():
print('hello 1')
# Option 2
with my_context if my_condition else contextlib.ExitStack():
print('hello 2')