Reputation: 73
If I want to work with two files I can write:
with open(fname1, 'r') as f1, open(fname2, 'r') as f2:
# do stuff with f1 and f2
But what if I have a list of paths (say, from glob.glob)? Can I do something analogous in a list comprehension? I have in mind something like:
with [open(path, 'r') for path in paths_list] as flist:
# do stuff with this list of open file objects
As written, this doesn't work.
Upvotes: 3
Views: 298
Reputation: 49856
The object of a with
statement must be a context manager. So, no, you can't do this with a list, but you might be able to do it with a custom container.
See: http://docs.python.org/2/library/contextlib.html
Or, for 3.3+ there's this: http://docs.python.org/dev/library/contextlib.html#contextlib.ExitStack (Note, as per arbarnert's answer, this can be used in 2.7, using contextlib2. See his answer for the link.)
The actual solution here is probably to If you're not going to use contextlib2
put the context manager in a loop:
for path in paths_list:
with open(path, 'r') as f:
#whatever
pass
Edit: Obviously, the above will open your files one at a time. There are relatively few use cases where you need to keep an undetermined number of files open at once.
Edit: To have multiple files open at once, ExitStack
is the solution you are looking for.
Upvotes: 8
Reputation: 365935
In 3.3+, ExitStack
is definitely the answer; in fact, it's the first example given in the docs:
with ExitStack() as stack:
files = [stack.enter_context(open(path) for path in path_list]
for f in files:
do_something(f)
Of course if your with
body is really just a loop over files
, there's no reason to do this—just put a with
statement for each file inside the loop. (In fact, there's a good reason not to do this—why open a possibly unbounded number of file handles at once just to use them one at a time?) But presumably your real code needs to use multiple files at the same time.
In earlier versions, you can easily just borrow ExitStack
from the 3.3 source. Backporting to 3.2 is trivial; for 2.7, you need to strip out (or rewrite, if you need it) the stuff that gets fancy with exception propagation to guarantee you the right exception contexts, but that's pretty easy.
However, an even better solution is probably to install contextlib2
off PyPI, which "provides backports of features in the latest version of the standard library’s contextlib module to earlier Python versions." Then you can just use contextlib2.ExitStack
instead of contextlib.ExitStack
. (In fact, contextlib2
had ExitStack
, under its preliminary name ContextStack
, before Python 3.3 did…)
But you can also easily build a closing_all
context manager, similar to the stdlib's closing
but for multiple things:
@contextlib.contextmanager
def closing_all(things):
try:
yield things
finally:
for thing in things:
thing.close()
If you need to deal with things whose close
method can raise, you need to be a little smarter—but with file objects, and most other types you'd using with closing
, you don't need that.
The bigger problem is that if any open
can raise an exception, it's hard to find any valid sequence you can actually pass as the things
argument. But if that's not a problem, using it is even simpler than ExitStack
:
with closing_all(open(path) for path in path_list) as files:
for f in fs:
do_something(f)
You could also build an opening_all(paths, mode='r')
that does the open
s for you and wraps them in a closing_all
, but I don't think that adds much.
Of course if you need to do this often, the best answer is to build opening_all
around ExitStack
and not even bother with closing_all
.
Upvotes: 6