29

Czy istnieje sposób na rozpoczęcie bloku kodu za pomocą instrukcji z instrukcją, ale warunkowo?Warunkowo z instrukcją w języku Python

Coś jak:

if needs_with(): 
    with get_stuff() as gs: 

# do nearly the same large block of stuff, 
# involving gs or not, depending on needs_with() 

celu wyjaśnienia, jeden scenariusz miałby blok zamknięty w ze stwierdzeniem, natomiast inna możliwość byłby taki sam blok, ale nie zamknięte (czyli jakby nie było wcięte)

Początkowe eksperymenty oczywiście dać błędy wcięcia ..

+2

napisać funkcję dla organizmu WITH? –

Odpowiedz

28

Jeśli chcesz uniknąć duplikowania kodu i korzystania z wersji Pythona przed 3.3 (gdzie contextlib.ExitStack nie jest dostępny), ty mógłby zrobić coś takiego:

class dummy_context_mgr(): 
    def __enter__(self): 
     return None 
    def __exit__(self, exc_type, exc_value, traceback): 
     return False 

lub:

import contextlib 

@contextlib.contextmanager 
def dummy_context_mgr(): 
    yield None 

a następnie użyć go jako:

with get_stuff() if needs_with() else dummy_context_mgr() as gs: 
    # do stuff involving gs or not 

Alternatywnie możesz zwrócić get_stuff() zwracać różne rzeczy na podstawie needs_with().

+3

Ten menedżer kontekstów powinien znajdować się w standardowej bibliotece Pythona, imho. Dzięki za to. – jjmontes

+0

Co powiesz na używanie nazw *, _... * i * dont *. Następnie takie instrukcje będą czytać 'z get_stuff() if should_get_stuff() else dont() jak gs:'? –

+0

@RiazRizvi Nie nazwałbym tego w ten sposób osobiście; Używałem nazwisk z pytania. – jamesdlin

3

można użyć contextlib.nested umieścić 0 lub więcej menedżerów kontekstowych w jednej with oświadczeniu.

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

To rozwiązanie ma swoje dziwactwa i zauważyłem, że od wersji 2.7 jest on przestarzały. Napisałem własnego menedżera kontekstu, aby poradzić sobie z żonglowaniem wieloma menedżerami kontekstu. Jego pracował dla mnie do tej pory, ale ja naprawdę nie uznać conditons krawędzi

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

Jak już wspomniałem w mojej odpowiedzi, python 3.3 dodał "contextlib.ExitStack", który wydaje się robić bardzo dużo tego, co robi twoja grupa "ContextGroup". Powiem, że jestem trochę zaskoczony, że nie został przeniesiony, ale jeśli chcesz mieć python> = 3.3, może to być dobra alternatywa dla ciebie. – Mike

+0

'contextlib2' jest pakietem pypi, który skopiował' ExitStack' do Pythona 2 –

25

Python 3.3 wprowadził contextlib.ExitStack dla tego rodzaju sytuacji. Daje ci "stos", do którego w razie potrzeby dodajesz menedżerów kontekstu. W twoim przypadku, to możesz to zrobić:

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

Wszystko, co jest wpisany do stack jest automatycznie exit ed na końcu zestawienia with jak zwykle. (Jeśli nic nie zostanie wprowadzone, nie stanowi to problemu.) W tym przykładzie wszystko, co jest zwracane przez get_stuff(), jest automatycznie edytowane przez exit.

Jeśli musisz użyć wcześniejszej wersji Pythona, możesz użyć modułu contextlib2, chociaż nie jest to standard. Powraca to i inne funkcje do wcześniejszych wersji Pythona. Możesz nawet wykonać import warunkowy, jeśli podoba ci się to podejście.

+0

+1, to powinna być wybrana odpowiedź. Jak wskazano [tutaj] (https://docs.python.org/3/library/contextlib.html?highlight=contextmanager#simplifying-support-for-single-optional-context-managers), ma on zająć się tym rodzajem problemu. Może być również użyty jako sprytny jednolinijkowy: 'with get_stuff() if needs_with() else ExitStack() jako gs'. – farsil

+0

@AnthonySottile Myślałem, że to jest to, co powiedziałem. – Mike

+0

derp, całkowicie to przegapiłem!/me usuwa –

5

Opcja firmami, aby osiągnąć dokładnie to:
https://pypi.python.org/pypi/conditional

from conditional import conditional 

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

Czy obsługuje klauzulę 'as ...' na końcu instrukcji 'with'? –

+1

patrząc na źródło ... tak to robi. 'with conditional (needs_with(), get_stuff()) as stuff:' da ci odwołanie do menedżera kontekstów 'get_stuff()' (wtedy i tylko wtedy, gdy warunek zostanie spełniony, w przeciwnym razie otrzymasz 'None') – Anentropic