Python中的条件语句
Posted
技术标签:
【中文标题】Python中的条件语句【英文标题】:Conditional with statement in Python 【发布时间】:2015-03-04 09:06:49 【问题描述】:有没有办法用 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 语句中包含一个块,而另一种可能是同一个块,但没有被封装(即,好像它没有缩进)
初始实验当然会出现缩进错误..
【问题讨论】:
为with的主体写一个函数? 【参考方案1】:import contextlib
my_context = None # your context
my_condition = False # your condition
with my_context if my_condition else contextlib.ExitStack():
print('hello')
【讨论】:
【参考方案2】:我发现@Anentropic answer 不完整。
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
仍然必须返回可输入对象,否则:AttributeError: 'NoneType' object has no attribute '__enter__'
【讨论】:
您仍然只向conditional
传递了两个参数,这并没有在我的原始答案中添加任何内容......看起来您的问题只是正确准备了所需的两个参数
@Anentropic 我的观点是,仅仅让conditional
工作是不够的,还需要一些变通方法来使它工作,这不像你的答案那么简单,并使整个事情不那么适用。 【参考方案3】:
所以我编写了这段代码; 它是这样调用的:
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 https://***.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'])
【讨论】:
【参考方案4】:如果您想避免重复代码并使用 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
您也可以让get_stuff()
根据needs_with()
返回不同的东西。
(请参阅 Mike's answer 或 Daniel's answer 了解您可以在以后的版本中执行的操作。)
【讨论】:
这个上下文管理器应该在标准python库中,恕我直言。谢谢你。 如何使用名称should_... 和dont。那么像这样的语句会写成with get_stuff() if should_get_stuff() else dont() as gs:
?
@RiazRizvi 我个人不会那样命名它;我使用的是问题中的名称。
@jjmontes contextlib.ExitStack(新的python 3.3)可以用作虚拟上下文管理器。【参考方案5】:
很难找到 @farsil 漂亮的 Python 3.3 单行代码,所以这里有它自己的答案:
with ExitStack() if not needs_with() else get_stuff() as gs:
# do stuff
请注意,ExitStack 应该排在第一位,否则 get_stuff()
将被评估。
【讨论】:
Note that ExitStack should come first, otherwise get_stuff() will be evaluated
— 不会【参考方案6】:
从 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
否则它只会返回None
:
>>> with nullcontext() as value:
... print(value)
...
None
它非常简洁,请在此处查看文档:https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext
【讨论】:
这提出了一个问题,在输入 with 语句之前调用 get_stuff() 是否总是安全的?with open(file) as fh
是否等同于 f = open(file)
后跟 with f as fh
?
这取决于你的上下文管理器做什么。大多数上下文管理器不应该在他们的__init__
方法中做事,而只在他们的__enter__
(或__aenter__
)方法上做事,该方法在with
语句中使用时被调用。所以不幸的是,答案是“这取决于”。如果您对此感到担心,则可以将函数分配给 cm
而不调用它们(如有必要,使用 functools.partial
),然后执行 with cm() as gs
。
单行将是:with get_stuff() if needs_with() else nullcontext() as gs
【参考方案7】:
Python 3.3 针对这种情况引入了contextlib.ExitStack
。它为您提供了一个“堆栈”,您可以根据需要向其中添加上下文管理器。在你的情况下,你会这样做:
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。如果您喜欢这种方法,您甚至可以进行条件导入。
【讨论】:
+1,这应该是选择的答案。正如here 所指出的,它旨在处理此类问题。此外,它还可以用作漂亮的单行:with get_stuff() if needs_with() else ExitStack() as gs
。【参考方案8】:
实现此目的的第三方选项:https://pypi.python.org/pypi/conditional
from conditional import conditional
with conditional(needs_with(), get_stuff()):
# do stuff
【讨论】:
它是否支持with
语句末尾的as ...
子句?
查看源代码...是的。 with conditional(needs_with(), get_stuff()) as stuff:
将为您提供对 get_stuff()
上下文管理器的引用(当且仅当满足条件时,否则您将获得 None
)
我发现你的回答不完整:***.com/questions/27803059/…【参考方案9】:
您可以使用 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
【讨论】:
正如我在回答中提到的,python 3.3 添加了contextlib.ExitStack
,这似乎与您的ContextGroup
所做的非常相似。我会说它没有被反向移植让我有点惊讶,但如果你愿意要求 python >=3.3,那对你来说可能是一个不错的健壮选择。
contextlib2
是一个 pypi 包,它已将 ExitStack
反向移植到 python 2以上是关于Python中的条件语句的主要内容,如果未能解决你的问题,请参考以下文章