29

条件付きで、しかしwithステートメントでコードブロックを開始する方法はありますか?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() 

を明確にすることはなかったかのように、1つのシナリオは、すなわち(別の可能性は、同じブロックになりながら、声明で包まれたブロックを持っていますが、包まれないだろう当然の)インデント

最初の実験は、インデントのエラーを与える。..

+2

が持つのボディのための関数を書きますか? –

答えて

28

と考えられていません

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()が別のものを返すようにすることもできます。

+3

このコンテキストマネージャは標準のPythonライブラリimhoになければなりません。ありがとう。 – jjmontes

+0

名前は*〜_... *と* dont *を使用するのはどうですか? should_get_stuff()else dont()をgs: 'とすると、このような文は' get_stuff()で読み込まれます。 –

+0

@RiazRizvi私はそれを個人的にそのように名付けなかったでしょう。私はその質問の名前を使用していました。 – jamesdlin

3

あなたは、単一のwithステートメントに0以上のコンテキストマネージャを入れてcontextlib.nestedを使用することができます。

>>> 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の時点で廃止されていることに気付きました。私は、複数のコンテキストマネージャを扱うための独自のコンテキストマネージャを書いていました。 ITSは、これまで私のために働いていますが、コードの重複を避けたいとあなたは、(contextlib.ExitStackが利用できない場合)前に3.3へのPythonのバージョンを使用している場合は、私は本当にエッジconditons

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 
+1

私の答えで言及したように、python 3.3では 'contextlib.ExitStack'が追加されました。これはあなたの' ContextGroup'がしていることを非常によく表しています。私はそれがバックポートされていないことに少し驚いていると言いますが、Python> = 3.3を必要としたいなら、それは素晴らしい堅牢な代替手段かもしれません。 – Mike

+0

'contextlib2'は、Python 2に' ExitStack'をバックポートしたpypiパッケージです。 –

25

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編です。 (何も入力されていなければ問題ありません)この例では、get_stuff()によって返されるものはすべて自動的にexitです。

以前のバージョンのPythonを使用する必要がある場合は、標準ではありませんが、contextlib2モジュールを使用することができます。これは、この機能と他の機能を以前のバージョンのPythonにバックポートします。このアプローチが好きなら、条件付きインポートを行うこともできます。

+0

+1、これは選択された回答でなければなりません。ここで指摘されているように(https://docs.python.org/3/library/contextlib.html?highlight=contextmanager#simplifying-support-for-single-optional-context-managers)、この種のことを扱うことを意図しています問題のまた、それはもう1つの素晴らしいライナーとして使うことができます: 'else_ExtStack()をgs'として、needs_with()でget_stuff()を使用します。 – farsil

+0

@AnthonySottile私はそれが私の言ったことだと思った。 – Mike

+0

derp、私はそれを完全に逃した!/meが削除されます –

5

まさにこれを達成するために、サードパーティのオプションは:
https://pypi.python.org/pypi/conditional

from conditional import conditional 

with conditional(needs_with(), get_stuff()): 
    # do stuff 
+0

'with'文の終わりに' as ... '句をサポートしていますか? –

+1

ソースを見て...はいそうです。条件付き(needs_with()、get_stuff())をstuffとして: 'get_stuff()'コンテキストマネージャへの参照を与えます(条件が満たされている場合のみ、そうでない場合は 'None'を取得します) – Anentropic

関連する問題