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ステートメントにブロックが含まれていますが、別の可能性は同じブロックですが、ケースに入れられていません(つまり、インデントされていないかのように)
もちろん、最初の実験ではインデントエラーが発生します。
コードの重複を避けたい場合、バージョンPython 3.7より前(_contextlib.nullcontext
_が導入された場合)または3.3(_contextlib.ExitStack
_が導入された場合)を使用している場合、次のようなことができます:
_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()
に基づいて異なるものを返すようにすることもできます。
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
に入力されたものは、通常どおりexit
ステートメントの最後に自動的にwith
edされます。 (何も入力しない場合、問題はありません。)この例では、get_stuff()
によって返されるものはすべて、自動的にexit
edになります。
以前のバージョンのpythonを使用する必要がある場合、 _contextlib2
_ モジュールを使用できる場合がありますが、これは標準ではありません。この機能やその他の機能を以前のバージョンの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
はほとんどノーオペレーションコンテキストマネージャーです。 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
これを正確に達成するためのサードパーティオプション:
https://pypi.python.org/pypi/conditional
from conditional import conditional
with conditional(needs_with(), get_stuff()):
# do stuff
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
@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()
を呼び出しません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://stackoverflow.com/a/3655857/997253
"""
LMBD = lambda:0
islambda=isinstance(f, type(LMBD)) and f.__== 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'])
_