Python中的条件with语句
有没有一种方法可以用with语句开始一段代码,但要有条件 比如:Python中的条件with语句,python,conditional,indentation,conditional-statements,with-statement,Python,Conditional,Indentation,Conditional Statements,With Statement,有没有一种方法可以用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() class dummy_context_mgr(): def __enter__(self): return None de
if needs_with():
with get_stuff() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
class dummy_context_mgr():
def __enter__(self):
return None
def __exit__(self, exc_type, exc_value, traceback):
return False
为了澄清这一点,一个场景将在with语句中封装一个块,而另一种可能是相同的块,但不封装(即,好像它没有缩进)
初始实验当然会产生缩进错误。您可以使用
contextlib.nested
将0个或多个上下文管理器放入带有语句的单个中
>>> 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
如果您希望避免重复代码,并且正在使用3.7(引入contextlib.nullcontext
时)甚至3.3(引入contextlib.ExitStack
时)之前的Python版本,您可以执行以下操作:
if needs_with():
with get_stuff() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
class dummy_context_mgr():
def __enter__(self):
return None
def __exit__(self, exc_type, exc_value, traceback):
return False
或:
然后将其用作:
with get_stuff() if needs_with() else dummy_context_mgr() as gs:
# do stuff involving gs or not
您也可以使用()使get\u stuff()
根据需要返回不同的东西
(请参阅或了解在更高版本中可以做什么。)针对这种情况引入的Python 3.3。它为您提供了一个“堆栈”,您可以根据需要向其中添加上下文管理器。在您的情况下,您可以这样做:
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()
输入到堆栈
中的任何内容都会像往常一样在语句的末尾自动退出
ed。(如果没有输入任何内容,这不是问题。)在本例中,由get_stuff()
返回的任何内容都将被exit
自动删除
如果您必须使用早期版本的python,则可以使用该模块,尽管这不是标准的。它将此功能和其他功能向后移植到python的早期版本。如果您喜欢这种方法,您甚至可以进行有条件的导入。第三方选项可以实现这一点:
从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
几乎就是一个无操作的上下文管理器。如果依赖于后面的现有内容,则可以向其传递一个参数,该参数将产生:
>>> with nullcontext(5) as value:
... print(value)
...
5
否则它只会返回None
:
>>> with nullcontext() as value:
... print(value)
...
None
它非常简洁,请查看这里的文档:很难找到@farsil漂亮的Python 3.3一行程序,因此这里有它自己的答案:
with ExitStack() if not needs_with() else get_stuff() as gs:
# do stuff
请注意,ExitStack应该放在第一位,否则将计算get\u 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()
特性:
除非条件为true,否则它不会调用get\u stuff()
如果条件为false,则提供一个虚拟contextmanager。(对于python>=3.7,可能会替换为contextlib.nullcontext
)
如果条件为false,您也可以选择发送另一个contextmanager:
将c_with(needs_with(),lambda:get_stuff(),lambda:dont_get_stuff())作为gs:
希望这能帮助别人
--代码如下:
def call_if_lambda(f):
"""
Calls f if f is a lambda function.
From https://stackoverflow.com/a/3655857/997253
"""
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)
完整的有条件的使用需要3个条件,而不是1个条件,因为:
name错误:如果未定义名称“b”,则名称“b”未定义
函数foo
仍然必须返回可输入的对象,否则:AttributeError:'NoneType'对象没有属性'\uuuuuu enter\uuu'
为with的主体编写函数?正如我在回答中提到的,python 3.3添加了contextlib.ExitStack
,这似乎与ContextGroup
的功能非常相似。我会说,我有点惊讶它没有被后端口,但是如果你愿意要求python>=3.3,那可能是一个很好的健壮的替代方案。contextlib2
是一个pypi包,它已经将ExitStack
后端口到python 2。这个上下文管理器应该在标准python库imho中。谢谢你。用你应该知道的名字怎么样。。。别这样。然后,像这样的语句会读为带有get_stuff()的,如果get_stuff()或者不作为gs:
?@RiazRizvi我个人不会这样命名;我使用的是问题中的名称。@jjmontes(新的python 3.3)可以用作虚拟上下文管理器。它是否支持as…
子句(位于with
语句末尾)?查看源代码。。。是的<代码>带有条件(需要使用,获取内容())作为内容:
将为您提供对获取内容()
上下文管理器的引用(如果且仅当条件满足,否则您将获取无
)我发现您的答案不完整:+1,这应该是所选答案。正如所指出的,它是为了解决这类问题。此外,它还可以用作一个漂亮的单行程序:如果需要,则使用get_stuff(),否则将作为gs退出stuck()。您仍然只将两个参数传递给conditional
,这并没有给我的原始答案添加任何内容。。。看来你的问题就在于正确准备这两个论点needed@Anentropic我的观点是,仅仅让有条件的
发挥作用是不够的,另外还需要一些变通办法来让它发挥作用,这些办法不像你的答案那么简单,而且会降低整个问题的适用性。请注意,ExitStack应该放在第一位,否则将对get_stuff()进行求值
-不,这不会引起问题,但在输入with语句之前调用get_stuff()是否总是安全的?将open(file)作为fh
的是否等同于将f=open(file)
后跟将f作为fh的?这取决于上下文管理器的操作。大多数上下文管理器不应该在其\uuuuu init\uuuuuuu
方法中执行操作,而只应该在其\uuuuuuuuu enter\uuuuuuuu
方法(或\uuuuuu aenter\uuuuuuuuuu
)中执行操作,该方法在with
语句中使用时调用。不幸的是,答案是“视情况而定”。如果您对此感到担心,可以将函数分配给cm
,而无需调用它们(使用functools.parti)
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)