§16 gives a useful overview of writing to files, but we find that a few subjects it discusses could do with additional clarification, and there are a few useful practices it omits to mention.

1 Always use with when writing

§16.2.3 mentions the use of the with keyword, but most of the example code in the chapter does not use it. Using with is safer and more robust than using open/close and should be your default any time you plan to write to a file.

Thus, listing1606.py’s file-writing part should be written as

with open('pc_writetext.tmp', 'w') as fp:
    while True:
        text = input('Please enter a line of text: ')
        if text == '':
            break
        fp.write(text)

and listing1607.py’s file-writing part should be written as

with open(FILENAME, 'a') as fp:
    while True:
        text = input('Please enter a line of text: ')
        if text == '':
            break
        fp.write(text)

2 print is usually better than write

It is often more convinient to use the built-in function print than the file method write. print accepts an optional argument file which can be used to print to a file.

with open(FILENAME, 'a') as fp:
    while True:
        text = input('Please enter a line of text: ')
        if text == '':
            break
        print('text =', text, file=fp)

Note that

  1. the file= argument must come after any arguments to be printed
  2. the file= argument must be an opened file, preferably using a with construct

print behaves the way you expect it would: accepts multiple arguments of any type, puts spaces between them, and goes to a new line after each invocation.

For completeness, print also has several other optional arguments:

  • print(1, 2, 3, sep='☺') will print 1☺2☺3\n instead of the default 1 2 3\n
  • print(1, 2, 3, end='☺') will print 1 2 3☺ instead of the default 1 2 3\n
  • flush is described below.

3 More on buffering

§16.1.3 mentions buffering and flushing, as does a footnote in §16.10. A bit more on this is worth understanding.

It is more efficient for computers to read and write files in large chunks than to read or write them in small chunks. Because of that, Python will work with your operating system (OS) to try to turn all your small reads and writes into large reads and writes.

Conceptually, file writing might proceed something like this:

Code Python+OS buffer Disk
print(3, file=f) 3\n
print(4, file=f) 3\n4\n
print('some text', file=f) 3\n4\nsome text\n
print(6, file=f) 3\n4\nsome text\n6\n
print(7, file=f) 7\n 3\n4\nsome text\n6\n

The operating system can chose to flush its buffer to disk at any time, or not to do so. However, four things force a flush to happen:

  • Invoking f.close().

  • Exiting a with block, which implicitly calles f.close().

    Note that with blocks are existed if there is an exeption too. The following code:

    f = open('test.txt', 'w')
    print('did this appear?', file=f)
    x = 3 / 0
    f.close()

    does not guarantee that test.txt has anything written to it because there was a divide-by-zero exception before the close was called. However, the following code:

    with open('test.txt', 'w') as f:
        print('did this appear?', file=f)
        x = 3 / 0

    does guarantee that test.txt has did this appear? written to it because existing a with via a divide-by-zero exception is still existing the with, and thus closes the file.

  • Invoking f.flush(). Unlike close, flush leaves the file open after ensuring the buffer is written to disk.

  • Using the flush=True optional argument of print, like print(3, '+', 4, '=', 3+4, file=f, flush=True)

Buffering also happens on file reads, but we won’t see cases where that matters much in this course.