web-dev-qa-db-ja.com

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ステートメントにブロックが含まれていますが、別の可能性は同じブロックですが、ケースに入れられていません(つまり、インデントされていないかのように)

もちろん、最初の実験ではインデントエラーが発生します。

53
nicole

コードの重複を避けたい場合、バージョン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()に基づいて異なるものを返すようにすることもできます。

(後のバージョンでできることについては マイクの答え または ダニエルの答え をご覧ください。)

44
jamesdlin

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ステートメントの最後に自動的にwithedされます。 (何も入力しない場合、問題はありません。)この例では、get_stuff()によって返されるものはすべて、自動的にexitedになります。

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

51
Mike

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

9
Daniel Porteous

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

from conditional import conditional

with conditional(needs_with(), get_stuff()):
    # do stuff
9
Anentropic

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
4
tdelaney

@farsilの気の利いたPython 3.3ワンライナーを見つけるのは困難だったので、ここにそれ自身の答えがあります:

_with ExitStack() if not needs_with() else get_stuff() as gs:
     # do stuff
_

ExitStackが最初に来る必要があることに注意してください。そうでない場合、get_stuff()が評価されます。

2
skeller88

そこで、このコードを作成しました。次のように呼び出されます。

_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()
_

プロパティ:

  1. 条件が真でない限り、get_stuff()を呼び出しません
  2. 条件がfalseの場合、ダミーのcontextmanagerが提供されます。 (おそらくpython> = 3.7の場合は_contextlib.nullcontext_に置き換えることができます)
  3. オプションで、条件がfalseの場合に別のコンテキストマネージャーで送信できます。
    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'])
_