Opening a File with
The last lesson ended with seven lines of try and finally wrapped around two lines of actual work, all so that one file would definitely get closed. Python has a statement that does the whole of that, and it is four letters long.
1The shape of a with block
with open('notes.txt', 'r') as f:is where the file is open. The moment the indenting stops, the file is closed.
Read it as a sentence: with this file open as f, do this. The colon and the indented block are the same ones you write after if and for — nothing new there.
with open('notes.txt', 'r') as f:
print(f.read())
print('inside the block, closed?', f.closed)
print('after the block, closed?', f.closed)Python is easy to learn. A file keeps your data safe. Practice every day. inside the block, closed? False after the block, closed? True
f.close(), and the file is closed. That is the entire point of the statement: the block closes the file on the way out, whichever way out it takes.2The same program, both ways
f = open('notes.txt', 'r')
try:
print(f.read())
finally:
f.close()Five lines, of which one does the work.
with open('notes.txt', 'r') as f:
print(f.read())Two lines, and the closing is not something you can forget.
3The case that matters: a crash inside the block
Shorter is not the reason to use it. This is. Both programs below break in the middle, and only one of them leaves the file shut:
try:
f = open('notes.txt', 'r')
print('the file is open')
print(10 / 0)
f.close()
except ZeroDivisionError:
print('The program crashed before close() ran.')
print('closed?', f.closed)the file is open The program crashed before close() ran. closed? False
try:
with open('notes.txt', 'r') as f:
print('the file is open')
print(10 / 0)
except ZeroDivisionError:
print('The program crashed inside the block.')
print('closed?', f.closed)the file is open The program crashed inside the block. closed? True
try is still there, and it is still doing the catching. with handles closing, not errors. Take the try away and the ZeroDivisionError ends the program exactly as before — with the file neatly closed on the way out.The same goes for a file that is not there. with cannot help with that either, because the failure happens in open(), before there is anything to close:
with open('marks.txt', 'r') as f:
print(f.read())Traceback (most recent call last):
File "with_missing.py", line 1, in <module>
with open('marks.txt', 'r') as f:
^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'marks.txt'Which means the two work together, and this is the shape most real programs end up with:
try:
with open('marks.txt', 'r') as f:
print(f.read())
except FileNotFoundError:
print('marks.txt is not in this folder.')marks.txt is not in this folder.
4f still exists afterwards — and is closed
The name f does not disappear at the end of the block. It is an ordinary variable holding a now-closed file object, so using it is the ValueError from the last lesson:
with open('notes.txt', 'r') as f:
data = f.read()
print(data)
print(f.read())Python is easy to learn.
A file keeps your data safe.
Practice every day.
Traceback (most recent call last):
File "after_the_block.py", line 5, in <module>
print(f.read())
^^^^^^^^
ValueError: I/O operation on closed file.data was filled inside the block, so printing it afterwards is fine — the string is just a string. Reading f again afterwards is not: that needs the file, and the file is shut.5Two files in one with
Copying, filtering, splitting a file — all of them need two files open together. Separate them with a comma and both are closed at the end of the block:
with open('notes.txt', 'r') as source, open('copy.txt', 'w') as target:
target.write(source.read())
print(open('copy.txt').read())Python is easy to learn. A file keeps your data safe. Practice every day.
6Try it
7Recap
Opens the file, puts the handle in f, and runs the indented block with it open.
At the end of the block, always — normally, on an exception, or on a return out of a function.
Writing one is not wrong, only pointless. The block will close it again, which is harmless.
A FileNotFoundError or a crash inside the block still needs try / except around it.
The variable survives; the connection does not. Save what you read into another variable inside the block.
Separate them with a comma. Both are closed at the end.
- 1
Rewrite one of your open/close programs as a
withblock.Hint · Two lines disappear: the close(), and usually the try.
- 2
Print
f.closedinside the block and again after it.Hint · False inside, True outside — the block did it for you.
- 3
Put a deliberate
10 / 0inside awithblock and checkf.closedafterwards.Hint · Catch the ZeroDivisionError, or the print never runs.
- 4
Copy every line of one file into another using a single
with.Hint · Two open() calls, one comma, one block.
What does the with clause do that open() alone does not?
After a with block ends, what is f?
Which of these opens two files in one with statement?
Does a with block need f.close() at the end?