2

I have an array of files:

    part_files = [open(name, "w+") for name in part_names]
    ...
    [part.close() for part in part_files]

I would like to close them in a guaranteed manner even when there is an exception in between.

I am aware of two ways, namely try catch finally and contextlib.nested however I'd like to know which is preferred and would work on both 2.7 and 3.0. From what I understand contextlib.nested is deprecated in 3.0

iggy
  • 1,613
  • 1
  • 18
  • 35
  • `contextlib` is not deprecated, but `contextlib.nested()` *is*, as it won't handle exceptions when opening correctly. – Martijn Pieters Mar 27 '15 at 14:19
  • 1
    Related: [Alternative to contextlib.nested with variable number of context managers](https://stackoverflow.com/q/16083791) – Martijn Pieters Mar 27 '15 at 14:23
  • Since you open them only for write, isn't it possible to write to the files one after another? I usually hate those questions but do you _really_ need to have them open all at the same time? Otherwise you could use `with open(name, "w+") as fileobj:` within a foreach loop over the names. Which would actually be the preferred way afaik. – swenzel Mar 27 '15 at 14:27
  • If you use the try/finally approach, the trick is to use a `for` loop that has a try/except clause so that you continue after exceptions. – tdelaney Mar 27 '15 at 15:45

2 Answers2

4

In Python3.3+, you could use contextlib.ExitStack. In Python2 (or older versions of Python3), you could use contextlib2.ExitStack, which is installable with

pip install contextlib2

try:
    import contextlib
    contextlib.ExitStack
except AttributeError:
    import contextlib2 as contextlib

partnames = ['foo', 'bar', 'baz']
with contextlib.ExitStack() as stack:
    # All opened files will automatically be closed at the end of
    # the with statement, even if attempts to open files later
    # in the list raise an exception
    files = [stack.enter_context(open(name, "w+")) for name in partnames]
    print(files)
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • what does the 3rd line do (counting from 1)? – iggy Mar 27 '15 at 16:00
  • `contextlib.ExitStack` tests for the existence of `ExitStack` as an attribute of the module `contextlib`. In Python3.3+ it will already exist. In Python 2 it won't, so an `AttributeError` will be raised. The `AttributeError` is caught and handled by the `except`-suite, which tries to import `contextlib2`. – unutbu Mar 27 '15 at 19:15
  • we can say `from contextlib import ExitStack` to replace lines 2, 3 I guess? – iggy Mar 30 '15 at 10:18
  • Yes, you could do that, but then be sure to change `except AttributeError` to `except ImportError`. – unutbu Mar 30 '15 at 11:22
0

How about this:

part_files = []
try:
  for name in part_names:
    part_files.append(open(name, "w+"))
  ... do stuff ...
finally:
  for part in part_files:
    part.close()

If an exception is thrown while opening files, it will still close all the files that have been opened so far

user9876
  • 10,954
  • 6
  • 44
  • 66