926

I want to change a couple of files at one time, iff I can write to all of them. I'm wondering if I somehow can combine the multiple open calls with the with statement:

try:
  with open('a', 'w') as a and open('b', 'w') as b:
    do_something()
except IOError as e:
  print 'Operation failed: %s' % e.strerror

If that's not possible, what would an elegant solution to this problem look like?

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
Frantischeck003
  • 9,271
  • 3
  • 15
  • 4
  • 4
    Also a similar question: [Multiple variables in a 'with' statement?](https://stackoverflow.com/questions/893333/multiple-variables-in-a-with-statement) – Jeyekomon Oct 21 '20 at 12:39

8 Answers8

1417

As of Python 2.7 (or 3.1 respectively) you can write

with open('a', 'w') as a, open('b', 'w') as b:
    do_something()

(Historical note: In earlier versions of Python, you can sometimes use contextlib.nested() to nest context managers. This won't work as expected for opening multiples files, though -- see the linked documentation for details.)


In the rare case that you want to open a variable number of files all at the same time, you can use contextlib.ExitStack, starting from Python version 3.3:

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # Do something with "files"

Note that more commonly you want to process files sequentially rather than opening all of them at the same time, in particular if you have a variable number of files:

for fname in filenames:
    with open(fname) as f:
        # Process f
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Yes .. so cool that a few lines of code allow doing that in previous python versions as well, as in this example here: http://metapython.blogspot.com/2010/12/multiple-contests-in-with-statement-not.html – jsbueno Jan 06 '11 at 19:34
  • 9
    Unfortunately, according to the contextlib.nested docs, you shouldn't use it for file opening: "using nested() to open two files is a programming error as the first file will not be closed promptly if an exception is thrown when opening the second file." – weronika Sep 01 '11 at 20:49
  • 57
    is there a way to use `with` to open a variable list of files? – monkut Apr 10 '13 at 00:29
  • 31
    @monkut: Very good question (you could actually ask this as a separate question). Short answer: Yes, there is [`ExitStack`](http://docs.python.org/3/library/contextlib.html#contextlib.ExitStack) as of Python 3.3. There is no easy way of doing this in any earlier version of Python. – Sven Marnach Apr 10 '13 at 11:38
  • If one of the open()s fails, it is possible to determine which one it was? I'm thinking specifically of being able to give a detailed error message. – John Auld May 16 '13 at 21:48
  • @JohnAuld: Wrao the whole thing in a try/except block, catch the exception and look at it. It should have attributes with all the desired information. – Sven Marnach May 29 '13 at 21:30
  • 2
    @monkut You should definitely ask your question, because I have the exact same question. contextlib.ExitStack seems to be the answer. – tommy.carstensen May 30 '13 at 09:53
  • 16
    Is it possible to have this syntax span multiple lines? – tommy.carstensen Sep 30 '14 at 14:06
  • 11
    @tommy.carstensen: You can use the usual [line continuation mechanisms](https://docs.python.org/2.7/reference/lexical_analysis.html#explicit-line-joining). You should probably use backslash line continuation to break at the comma, as [recommended by PEP 9](http://legacy.python.org/dev/peps/pep-0008/#maximum-line-length). – Sven Marnach Oct 05 '14 at 23:27
  • In my do somthing I want to read 1 line at a time from each file. Is that possible? I want to read the first line from both files, then second line from both ones up to the end. The files will have the same number of lines. – thanos.a Aug 25 '17 at 13:59
  • 3
    @thanos.a You can use `for line_a, line_b in zip(a, b)`, where `a` and `b` are the two file objects as above. (In general, please ask a new question instead of commenting, or even better, search for existing questions first.) – Sven Marnach Aug 25 '17 at 18:02
  • The `ExitStack` example is useful even for a fixed list of files to open, since you can't use parentheses for implicit line continuation in the `with` statement's list of context managers. – chepner Jan 12 '20 at 16:50
130

Just replace and with , and you're done:

try:
    with open('a', 'w') as a, open('b', 'w') as b:
        do_something()
except IOError as e:
    print 'Operation failed: %s' % e.strerror
Michael
  • 8,920
  • 3
  • 38
  • 56
97

For opening many files at once or for long file paths, it may be useful to break things up over multiple lines. From the Python Style Guide as suggested by @Sven Marnach in comments to another answer:

with open('/path/to/InFile.ext', 'r') as file_1, \
     open('/path/to/OutFile.ext', 'w') as file_2:
    file_2.write(file_1.read())
Michael Ohlrogge
  • 10,559
  • 5
  • 48
  • 76
  • 2
    With this indentation I get: "flake8: continuation line over-indented for visual indent" – Louis M Jul 16 '18 at 13:44
  • @LouisM That sounds like something coming from your editor or environment, rather than base python. If it continues to be a problem for you, I'd recommend creating a new question relating to it and giving more detail on your editor and environment. – Michael Ohlrogge Jul 16 '18 at 15:01
  • 5
    Yes it is definitely my editor, and it is only a warning. What I wanted to emphasize is that your indentation does not comply with PEP8. You should indent the second open() with 8 spaces instead of aligning it with the first one. – Louis M Jul 18 '18 at 09:57
  • 3
    @LouisM PEP8 is a *guideline*, not rules, and in this case I would most certainly ignore it – Nick is tired Jul 23 '18 at 09:04
  • 4
    Yes no problem with that, it might be useful for other people with automatic linters though :) – Louis M Jul 23 '18 at 10:06
  • Very nice! I have been struggling with this construct to find a more readable way of using it, and this one does it! – Fontanka16 Jul 13 '21 at 11:40
56

From Python 3.10 there is a new feature of Parenthesized context managers, which permits syntax like:

with (
    open("a", "w") as a,
    open("b", "w") as b
):
    do_something()
Chris_Rands
  • 38,994
  • 14
  • 83
  • 119
33

Since Python 3.3, you can use the class ExitStack from the contextlib module to safely
open an arbitrary number of files.

It can manage a dynamic number of context-aware objects, which means that it will prove especially useful if you don't know how many files you are going to handle.

In fact, the canonical use-case that is mentioned in the documentation is managing a dynamic number of files.

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # 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

If you are interested in the details, here is a generic example in order to explain how ExitStack operates:

from contextlib import ExitStack

class X:
    num = 1

    def __init__(self):
        self.num = X.num
        X.num += 1

    def __repr__(self):
        cls = type(self)
        return '{cls.__name__}{self.num}'.format(cls=cls, self=self)

    def __enter__(self):
        print('enter {!r}'.format(self))
        return self.num

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit {!r}'.format(self))
        return True

xs = [X() for _ in range(3)]

with ExitStack() as stack:
    print(len(stack._exit_callbacks)) # number of callbacks called on exit
    nums = [stack.enter_context(x) for x in xs]
    print(len(stack._exit_callbacks))

print(len(stack._exit_callbacks))
print(nums)

Output:

0
enter X1
enter X2
enter X3
3
exit X3
exit X2
exit X1
0
[1, 2, 3]
Community
  • 1
  • 1
timgeb
  • 76,762
  • 20
  • 123
  • 145
29

Nested with statements will do the same job, and in my opinion, are more straightforward to deal with.

Let's say you have inFile.txt, and want to write it into two outFile's simultaneously.

with open("inFile.txt", 'r') as fr:
    with open("outFile1.txt", 'w') as fw1:
        with open("outFile2.txt", 'w') as fw2:
            for line in fr.readlines():
                fw1.writelines(line)
                fw2.writelines(line)

EDIT:

I don't understand the reason of the downvote. I tested my code before publishing my answer, and it works as desired: It writes to all of outFile's, just as the question asks. No duplicate writing or failing to write. So I am really curious to know why my answer is considered to be wrong, suboptimal or anything like that.

FatihAkici
  • 4,679
  • 2
  • 31
  • 48
  • 2
    i don't know what someone else downvoted you, but I UPVOTED you because this is the only example that had three files (one input, two output) which happened to be just what I needed. – Adam Michael Wood Dec 24 '17 at 00:41
  • 3
    @FatihAkici The Zen of Python says: "Flat is better than nested". Unnecessarily nested code lowers readability and is considered a bad practice. – Jeyekomon Oct 21 '20 at 12:21
  • @ElRuso why is it more pythonic? Less indentation? – gargoylebident May 19 '21 at 19:54
  • @stackexchange_account1111 yep, more detailed answer right above your question – El Ruso May 19 '21 at 22:10
  • Three files just is on (or beyond for some) the limit for when it would be appropriate to switch from nested or paranthesized-comma-separated context managers to an `ExitStack`. I like the plain expliciteness of this, but would then wrap the *fourth* level in a separate function `fanout(fr, fw1, fw2)` to reset the indentation, especially if the function does a little more with its input than shown in this example. – ojdo Jan 13 '23 at 12:55
6

With python 2.6 It will not work, we have to use below way to open multiple files:

with open('a', 'w') as a:
    with open('b', 'w') as b:
Aashutosh jha
  • 552
  • 6
  • 8
4

Late answer (8 yrs), but for someone looking to join multiple files into one, the following function may be of help:

def multi_open(_list):
    out=""
    for x in _list:
        try:
            with open(x) as f:
                out+=f.read()
        except:
            pass
            # print(f"Cannot open file {x}")
    return(out)

fl = ["C:/bdlog.txt", "C:/Jts/tws.vmoptions", "C:/not.exist"]
print(multi_open(fl))

2018-10-23 19:18:11.361 PROFILE  [Stop Drivers] [1ms]
2018-10-23 19:18:11.361 PROFILE  [Parental uninit] [0ms]
...
# This file contains VM parameters for Trader Workstation.
# Each parameter should be defined in a separate line and the
...
Pedro Lobito
  • 94,083
  • 31
  • 258
  • 268