2011-10-30 20 views
17

我知道我可以打开的东西,如多个文件,打开多个(数目不详),同时,确保他们正确关闭

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

但我有一个情况我有一个列表的文件打开,并且想知道当文件数量未知时,首选方法的做法是什么。喜欢的东西,

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

(但失败,因为列表中的AttributeError没有实现__exit__

我不使用类似介意,

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

    .... 

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

,但我不知道是什么如果某些文件在试图打开它们时抛出,就会发生。在finally区块中,是否会正确定义fs以及设法打开的文件?

+0

你们会访问这些文件并行,还是顺序? –

+0

@EthanFurman平行。 – tjm

回答

12

不,您的代码不会初始化fs,除非所有的open()调用都成功完成。这应该工作,虽然:

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

    .... 

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

还要注意的是f.close()可能会失败,所以你可能要赶上和忽略(或以其他方式处理)的任何故障出现。

1

尝试打开文件,尝试从文件中读取时,以及尝试关闭文件时(很少)会发生错误。

因此,一个基本的错误处理结构可能是这样的:

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

这确保了如果stream变量存在close将始终被调用。如果stream不存在,则open必须失败,因此没有要关闭的文件(在这种情况下,将立即执行except块)。

您是否真的需要将文件并行打开,还是可以按顺序处理?如果是后者,那么类似上面的文件处理代码应该放入一个函数中,然后调用列表中的每个路径。

7

当然,为什么不呢,这是一个应该做的配方。创建一个上下文管理器“池”,可以输入任意数量的上下文(通过调用它的enter()方法),它们将在套件结束时清理。

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 

例如:

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

注意事项:上下文管理器不应该在他们的__exit__()方法引发异常,但如果他们这样做,这个配方并不适用于所有的情况下做清理经理。同样,即使每个上下文管理器都指出应该忽略一个异常(通过从退出方法返回True),这仍然会允许异常被引发。

1

感谢您的所有答案。从大家的灵感中,我想出了以下几点。我认为(希望)它按照我的意图工作。我不确定是将它作为答案还是对问题的补充,但认为答案更合适,因为如果它没有按照我所要求的做出适当的评论,那么答案会更合适。

它可用于例如这样的..

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

或这样的..

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

,就是这样,

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> ')) 
相关问题