2011-10-30 22 views
17

Tôi biết rằng tôi có thể mở nhiều file với một cái gì đó như thế nào,Mở nhiều (một số không xác định) các tập tin cùng một lúc và đảm bảo họ được đóng lại một cách chính xác

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

Nhưng tôi có một tình huống mà tôi có một danh sách của các tệp để mở và tự hỏi phương thức ưa thích là làm như thế nào khi số lượng tệp không xác định trước. Một cái gì đó như thế nào,

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

(nhưng điều này không thành công với một AttributeError kể từ khi danh sách không thực hiện __exit__)

Tôi không quan tâm sử dụng một cái gì đó như thế nào,

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

    .... 

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

Nhưng không chắc chắn những gì sẽ xảy ra nếu một số tệp bị ném khi cố gắng mở chúng. fs có được định nghĩa đúng không, với các tệp đã quản lý để mở, trong khối finally?

+0

bạn sẽ được truy cập vào những file song song, hoặc tuần tự? –

+0

@EthanFurman Song song. – tjm

Trả lời

12

Không, mã của bạn sẽ không khởi tạo fs trừ khi tất cả các cuộc gọi open() hoàn tất thành công. Điều này sẽ làm việc mặc dù:

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

    .... 

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

Cũng lưu ý rằng f.close() có thể thất bại, do đó bạn có thể muốn bắt và bỏ qua (hoặc nếu không xử lý) bất kỳ thất bại đó.

1

Lỗi có thể xảy ra khi cố mở tệp, khi cố đọc từ tệp và (rất hiếm khi) khi cố gắng đóng tệp.

Vì vậy, một lỗi cấu trúc xử lý cơ bản có thể trông giống như:

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

này đảm bảo rằng close sẽ luôn luôn được gọi là nếu biến stream hiện hữu. Nếu stream không tồn tại thì open phải không thành công và do đó không có tệp để đóng (trong trường hợp đó, khối ngoại trừ sẽ được thực hiện ngay lập tức).

Bạn có thực sự cần mở các tệp song song hay chúng có thể được xử lý tuần tự không? Nếu sau này, sau đó một cái gì đó giống như mã xử lý tệp ở trên phải được đặt trong một hàm, sau đó được gọi cho mỗi đường dẫn trong danh sách.

7

Chắc chắn, tại sao không, Đây là công thức nên thực hiện. Tạo một trình quản lý ngữ cảnh 'pool' có thể nhập một số bối cảnh tùy ý (bằng cách gọi phương thức enter()) và chúng sẽ được dọn sạch ở cuối của bộ phần mềm.

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 

Ví dụ:

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

Hãy cẩn thận: các nhà quản lý bối cảnh không có nghĩa vụ để nâng cao ngoại lệ trong __exit__() phương pháp của họ, nhưng nếu họ làm, công thức này không làm sạch cho tất cả các bối cảnh người quản lý. Tương tự, ngay cả khi mọi trình quản lý ngữ cảnh chỉ ra rằng một ngoại lệ cần được bỏ qua (bằng cách trả lại True từ phương thức thoát của chúng), điều này sẽ vẫn cho phép ngoại lệ được nâng lên.

1

Cảm ơn tất cả câu trả lời của bạn. Lấy cảm hứng từ tất cả các bạn, tôi đã nghĩ ra những điều sau đây. Tôi nghĩ (hy vọng) nó hoạt động như tôi dự định.Tôi không chắc liệu có đăng câu trả lời hay bổ sung cho câu hỏi hay không, nhưng nghĩ rằng câu trả lời thích hợp hơn nếu như nó không làm được những gì tôi hỏi, nó có thể được bình luận một cách thích hợp.

Nó có thể được sử dụng ví dụ như thế này ..

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

hay như thế này ..

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

Và ở đây nó là,

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> ')) 
Các vấn đề liên quan