我知道我可以打开多个文件,比如
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()
但我不确定如果某些文件在尝试打开它们时会发生什么。在fs
区块中,finally
是否已正确定义,并且文件已设置为打开?
答案 0 :(得分:13)
不,除非所有fs
次调用成功完成,否则您的代码不会初始化open()
。这应该工作:
fs = []
try:
for f in files:
fs.append(open(f, 'rb'))
....
finally:
for f in fs:
f.close()
另请注意,f.close()可能会失败,因此您可能希望捕获并忽略(或以其他方式处理)任何失败。
答案 1 :(得分: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
),这仍然会允许引发异常。
答案 2 :(得分: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
这可确保在close
变量存在时始终会调用stream
。如果stream
不存在,则open
必定已失败,因此没有要关闭的文件(在这种情况下,将立即执行except块)。
您是否真的需要并行打开文件,还是可以按顺序处理?如果是后者,则应将类似上述文件处理代码的内容放入函数中,然后为列表中的每个路径调用该函数。
答案 3 :(得分: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> '))