2011-10-30 12 views
17

Zdaję sobie sprawę, że mogę otworzyć wiele plików z czymś,otwarcia (numer nieokreślone) plików na raz i upewniając się, że są prawidłowo zamknięte

with open('a', 'rb') as a, open('b', 'rb') as b: 

Ale Mam sytuacji gdzie mam listę plików do otwarcia i zastanawiam się, jaka jest preferowana metoda czynienia tego samego, gdy liczba plików jest nieznana z góry. Coś podobnego,

with [ open(f, 'rb') for f in files ] as fs: 

(ale to się nie powiedzie z AttributeError od listy nie implementuje __exit__)

nie przeszkadza coś,

try: 
    fs = [ open(f, 'rb') for f in files ] 

    .... 

finally: 
    for f in fs: 
     f.close() 

Ale nie jestem pewien, co stanie się, jeśli niektóre pliki wyrzucą próbując je otworzyć. Czy fs będzie poprawnie zdefiniowany, z plikami, które udało się otworzyć, w bloku finally?

+0

Będziesz dostępu do tych plików równolegle lub sekwencyjnie? –

+0

@EthanFurman Równolegle. – tjm

Odpowiedz

12

Nie, Twój kod nie zainicjowałby fs, chyba że wszystkie połączenia open() zostały pomyślnie zakończone. To powinno działać choć:

fs = [] 
try: 
    for f in files: 
     fs.append(open(f, 'rb')) 

    .... 

finally: 
    for f in fs: 
     f.close() 

Należy również zauważyć, że f.close() może nie tak może chcesz złapać i ignorować (lub inaczej obsłużyć) jakiekolwiek awarie tam.

1

Błędy mogą wystąpić podczas próby otwarcia pliku, podczas próby odczytu z pliku i (bardzo rzadko) podczas próby zamknięcia pliku.

więc struktura podstawowa obsługa błędu może wyglądać tak:

try: 
    stream = open(path) 
    try: 
     data = stream.read() 
    finally: 
     stream.close() 
except EnvironmentError as exception: 
    print 'ERROR:', str(exception) 
else: 
    print 'SUCCESS' 
    # process data 

Gwarantuje to close zawsze będzie nazywany jeśli zmienna stream istnieje. Jeśli stream nie istnieje, to open musi zakończyć się niepowodzeniem, a więc nie ma pliku do zamknięcia (w takim przypadku blok wyjątku zostanie natychmiast wykonany).

Czy na pewno trzeba otwierać pliki równolegle lub czy mogą one być przetwarzane sekwencyjnie? Jeśli to drugie, to coś takiego jak powyższy kod przetwarzania plików należy umieścić w funkcji, która jest następnie wywoływana dla każdej ścieżki na liście.

7

Jasne, dlaczego nie, Oto przepis, który powinien to zrobić. Utwórz "pulę" menedżera kontekstów, która może wprowadzić dowolną liczbę kontekstów (przez wywołanie jej jako metodę enter()) i zostaną one wyczyszczone na końcu zestawu.

class ContextPool(object): 
    def __init__(self): 
     self._pool = [] 

    def __enter__(self): 
     return self 

    def __exit__(self, exc_type, exc_value, exc_tb): 
     for close in reversed(self._pool): 
      close(exc_type, exc_value, exc_tb) 

    def enter(self, context): 
     close = context.__exit__ 
     result = context.__enter__() 
     self._pool.append(close) 
     return result 

Na przykład:

>>> class StubContextManager(object): 
...  def __init__(self, name): 
...   self.__name = name 
...  def __repr__(self): 
...   return "%s(%r)" % (type(self).__name__, self.__name) 
... 
...  def __enter__(self): 
...   print "called %r.__enter__()" % (self) 
... 
...  def __exit__(self, *args): 
...   print "called %r.__exit__%r" % (self, args) 
... 
>>> with ContextPool() as pool: 
...  pool.enter(StubContextManager("foo")) 
...  pool.enter(StubContextManager("bar")) 
...  1/0 
... 
called StubContextManager('foo').__enter__() 
called StubContextManager('bar').__enter__() 
called StubContextManager('bar').__exit__(<type 'exceptions.ZeroDivisionError'>, ZeroDivisionError('integer division or modulo by zero',), <traceback object at 0x02958648>) 
called StubContextManager('foo').__exit__(<type 'exceptions.ZeroDivisionError'>, ZeroDivisionError('integer division or modulo by zero',), <traceback object at 0x02958648>) 

Traceback (most recent call last): 
    File "<pyshell#67>", line 4, in <module> 
    1/0 
ZeroDivisionError: integer division or modulo by zero 
>>> 

Ostrzeżenia Zarządzający kontekst nie mają podnieść wyjątki w swoich __exit__() metod, ale jeśli tak, to przepis nie robi porządki dla wszystkich kontekście menedżerowie. Podobnie, nawet jeśli każdy menedżer kontekstów wskazuje, że wyjątek powinien zostać zignorowany (zwracając True z ich metod wyjścia), to nadal pozwoli na podniesienie wyjątku.

1

Dzięki za wszystkie odpowiedzi. Biorąc inspirację od was wszystkich, wymyśliłem następujące. Myślę (mam nadzieję), że działa tak, jak zamierzałem.Nie byłam pewna, czy zamieścić ją jako odpowiedź, czy jako dodatek do pytania, ale uznałam, że odpowiedź jest bardziej odpowiednia, ponieważ wtedy, gdy nie zrobi tego, o co prosiłem, może zostać odpowiednio zakomentowana.

Może być stosowany na przykład jak ten ..

with contextlist([open, f, 'rb'] for f in files) as fs: 
    .... 

lub tak ..

f_lock = threading.Lock() 
with contextlist(f_lock, ([open, f, 'rb'] for f in files)) as (lock, *fs): 
    .... 

I oto jest,

import inspect 
import collections 
import traceback 

class contextlist: 

    def __init__(self, *contexts): 

     self._args = [] 

     for ctx in contexts: 
      if inspect.isgenerator(ctx): 
       self._args += ctx 
      else: 
       self._args.append(ctx) 


    def __enter__(self): 

     if hasattr(self, '_ctx'): 
      raise RuntimeError("cannot reenter contextlist") 

     s_ctx = self._ctx = [] 

     try: 
      for ctx in self._args: 

       if isinstance(ctx, collections.Sequence): 
        ctx = ctx[0](*ctx[1:]) 

       s_ctx.append(ctx) 

       try: 
        ctx.__enter__() 
       except Exception: 
        s_ctx.pop() 
        raise 

      return s_ctx 

     except: 
      self.__exit__() 
      raise 


    def __exit__(self, *exc_info): 

     if not hasattr(self, '_ctx'): 
      raise RuntimeError("cannot exit from unentered contextlist") 

     e = [] 

     for ctx in reversed(self._ctx): 
      try: 
       ctx.__exit__() 
      except Exception: 
       e.append(traceback.format_exc()) 

     del self._ctx 

     if not e == []: 
      raise Exception('\n> '*2+(''.join(e)).replace('\n','\n> ')) 
Powiązane problemy