Persisting and Sharing Data

Text Files and Paths


Learning Objectives

  • You can read and write UTF-8 text using a with block.
  • You can distinguish a file’s contents, format, encoding, and filename extension.
  • You can distinguish whole-file text from line-by-line processing.
  • You can use .join() when constructing text from a list of strings.
  • You can explain relative paths and the effect of writing over an existing file.

A text file stores characters. Python can read those characters into strings or write strings out to a file. The list and string operations from earlier parts then do much of the remaining work.

Open a File for Writing

Start with a disposable file named practice_note.txt. The complete editor example below writes and reads it during the same Run, so you can try it here without creating a local folder.

with open("practice_note.txt", "w", encoding="utf-8") as file:
file.write("First line\n")
file.write("Second line\n")

open(...) receives a path, a mode, and here an encoding. The mode "w" means write. encoding="utf-8" is a keyword argument, using the same named-argument idea as key=... in Part 4.

Use encoding="utf-8" consistently when writing and reading the course’s text files. We will examine what an encoding describes after trying the first file operations.

The as file part binds the opened file to the name file. The indented block uses that file; it does not create a separate variable scope. Leaving the block closes it, including when an exception leaves the block early. We use this pattern instead of relying on remembering a separate close operation.

file.write(...) writes the supplied string. Unlike print(...), it does not automatically add a line break. The \n inside each string represents a newline character.

Writing Can Replace Existing Data

Opening an existing file in "w" mode truncates its previous contents. This happens when the file is opened, not only after the first successful write. Use the disposable practice filenames in these examples.

Read the Text Back

The complete program first writes the note, then opens it for reading. Predict its output before pressing Run.

No files opened Select a file to edit

The mode "r" means read; it is also the default when the mode argument is omitted. .read() returns the file’s complete text as a string. The string remains usable after the file has closed.

The extra empty-looking line after this output comes from print: the file’s text already ends in a newline, and print(text) adds another. It does not mean that Python inserted an extra record into the file.

For a small file, reading all the text at once is reasonable. When the task works line by line, direct iteration expresses that job more clearly.

Trace what the file operations print, including how writing again changes the file’s contents. Then arrange the write, read, and display steps.

The next exercise moves reading into a function. The caller chooses a path and receives the file’s text as the returned value.

Download the next exercise with the IDE plugin or Download starter code, and open its folder in your IDE. Write the function in main.py; message.txt is the sample input and check.py calls and tests your function. Save your changes and run py check.py on Windows or python3 check.py on macOS/Linux from that folder. The supplied check should print Checks passed..

The supplied from main import read_message line lets the checks call your function; the next chapter explains imports. Keep temporary calls in check.py so loading main.py does not perform file operations. If a named input file does not exist, let Python report FileNotFoundError; handling that failure comes later. Submit through the IDE plugin or upload a ZIP as described in the handout.

Read a message

0 / 10 points

Write read_message(path). Read the UTF-8 text file at the supplied path and return its complete text. Preserve spaces, blank lines, and whether its final line has a newline. Do not print or change the file. An empty file returns an empty string. Let a missing file raise FileNotFoundError. Files in this exercise use \n line endings. The sample contains Hello, Mäki! followed by a newline.

Work locally

Download this exercise with the Aalto OpenCS IDE plugin, or use Download starter code below and extract the ZIP into its own folder inside programming-practice. Open that exercise folder in your IDE. Keep each exercise in a separate folder.

FileRole
main.pyWrite read_message(path) here.
check.pySupplied sample assertions; add your own cases here.
message.txtSample input containing Hello, Mäki! followed by a newline.

Save your changes, then open the IDE terminal in this exercise folder: dir on Windows or ls on macOS/Linux should show the files above. Relative paths in the checks start from this folder. Run:

SystemRun the checks
Windowspy check.py
macOS/Linuxpython3 check.py

Use python instead if that is your working Python command. Successful supplied checks print Checks passed.. Read any failed assertion or traceback, fix your code, and rerun the checks. You may extend check.py; use separate data files for extra cases so the supplied samples retain their contents.

For example, check.py calls read_message("message.txt") and expects "Hello, Mäki!\n". Add a separate empty text file and assert that reading it returns "".

main.py defines functions; running it alone produces no output. check.py imports and calls those functions. Keep file operations inside the functions, with test calls in check.py, so importing main.py does not read or write data files or ask for input.

Submit your solution

Save your files. Submit using Submit Current Exercise in the IDE plugin, review the file list, and confirm Submit. Alternatively, create a ZIP from the exercise folder:

SystemCommand
Windowspy -m zipfile -c submission.zip main.py check.py message.txt
macOS/Linuxpython3 -m zipfile -c submission.zip main.py check.py message.txt

Upload submission.zip below and press Submit. The listed files must be at the archive’s top level, without an enclosing folder. Include the supplied support and sample files; leave out generated outputs, .venv, and __pycache__. Read the grading feedback after submitting. The grader also uses its own inputs; passing the sample checks alone does not establish the whole contract.

Process One Line at a Time

with open("practice_names.txt", "w", encoding="utf-8") as file:
file.write("Ada\nGrace\nLinus\n")
names = []
with open("practice_names.txt", "r", encoding="utf-8") as file:
for line in file:
names.append(line.strip())
assert names == ["Ada", "Grace", "Linus"]

A loop over a text file produces its lines. A line normally includes its ending newline, except that the last line need not have one. Here .strip() removes surrounding whitespace, which is appropriate because these names should not preserve indentation or trailing spaces.

That is a data decision. Do not automatically strip every line of every file: spaces might be meaningful in a poem, a diagram, or an exact text document.

The iteration must take place inside the with block. The open file is the resource that supplies lines; returning the file object and trying to read it after the block would try to use a closed file.

Read a name list

0 / 15 points

Write read_names(path). Read a UTF-8 file with one name per line. Strip surrounding whitespace, skip lines that then become empty, and return the remaining names in order. Preserve repeated names and spaces within a name. Do not print or modify the file. An empty file returns []; a missing file raises FileNotFoundError.

Work locally

Download this exercise with the Aalto OpenCS IDE plugin, or use Download starter code below and extract the ZIP into its own folder inside programming-practice. Open that exercise folder in your IDE. Keep each exercise in a separate folder.

FileRole
main.pyWrite read_names(path) here.
check.pySupplied assertions for reading and cleaning names; extend them.
names.txtSample input with whitespace, a blank line, and a repeated name.

Save your changes, then open the IDE terminal in this exercise folder: dir on Windows or ls on macOS/Linux should show the files above. Relative paths in the checks start from this folder. Run:

SystemRun the checks
Windowspy check.py
macOS/Linuxpython3 check.py

Use python instead if that is your working Python command. Successful supplied checks print Checks passed.. Read any failed assertion or traceback, fix your code, and rerun the checks. You may extend check.py; use separate data files for extra cases so the supplied samples retain their contents.

The supplied check calls read_names("names.txt") and expects ["Ada", "Grace", "Ada"]. For another case, create a separate file containing only blank lines and expect [].

main.py defines functions; running it alone produces no output. check.py imports and calls those functions. Keep file operations inside the functions, with test calls in check.py, so importing main.py does not read or write data files or ask for input.

Submit your solution

Save your files. Submit using Submit Current Exercise in the IDE plugin, review the file list, and confirm Submit. Alternatively, create a ZIP from the exercise folder:

SystemCommand
Windowspy -m zipfile -c submission.zip main.py check.py names.txt
macOS/Linuxpython3 -m zipfile -c submission.zip main.py check.py names.txt

Upload submission.zip below and press Submit. The listed files must be at the archive’s top level, without an enclosing folder. Include the supplied support and sample files; leave out generated outputs, .venv, and __pycache__. Read the grading feedback after submitting. The grader also uses its own inputs; passing the sample checks alone does not establish the whole contract.

Join Several Strings into One

Part 2 used .split() to turn text into a list. When writing text, we sometimes need the complementary operation:

names = ["Ada", "Grace", "Linus"]
text = "\n".join(names)
assert text == "Ada\nGrace\nLinus"

The separator is the string before .join(...). Python places it between the supplied strings. It does not add a separator at the beginning or end. Joining an empty list produces an empty string.

Every item must already be a string. A list of integers cannot be joined directly. Convert values deliberately while constructing a list of output strings when that is the task.

We can write the text from the three-name list above to a file:

with open("practice_exported_names.txt", "w", encoding="utf-8") as file:
file.write(text)
file.write("\n")

The second call adds a final newline. With an empty list, the first call would write no characters, but the second would still write a newline. Decide whether that is the intended empty-file behavior before using this pattern.

.join() constructs text; it does not know the rules of a structured format. In particular, manually joining arbitrary fields with commas is not a general CSV writer.

Write label lines

0 / 15 points

Write write_labels(path, labels). Labels is a list of strings without embedded newline characters. Preserve the strings exactly, including empty labels and spaces. Write UTF-8 text with one newline after every label, replacing any previous file contents. An empty list creates an empty file. Return None, without printing or changing labels.

For ["Ada", "Mäki"], the file text is "Ada\nMäki\n". For [""], it is one newline. Missing parent folders need not be created.

Work locally

Download this exercise with the Aalto OpenCS IDE plugin, or use Download starter code below and extract the ZIP into its own folder inside programming-practice. Open that exercise folder in your IDE. Keep each exercise in a separate folder.

FileRole
main.pyWrite write_labels(path, labels) here.
check.pySupplied checks that call your writer and read its output; extend them.

Save your changes, then open the IDE terminal in this exercise folder: dir on Windows or ls on macOS/Linux should show the files above. Relative paths in the checks start from this folder. Run:

SystemRun the checks
Windowspy check.py
macOS/Linuxpython3 check.py

Use python instead if that is your working Python command. Successful supplied checks print Checks passed.. Read any failed assertion or traceback, fix your code, and rerun the checks. You may extend check.py; use separate data files for extra cases so the supplied samples retain their contents.

The checks write practice_labels.txt with ["Ada", "Mäki"] and compare its text with "Ada\nMäki\n". They then write an empty list, so the file left after the checks is empty. You can open that output file in the IDE after the process ends.

main.py defines functions; running it alone produces no output. check.py imports and calls those functions. Keep file operations inside the functions, with test calls in check.py, so importing main.py does not read or write data files or ask for input.

Submit your solution

Save your files. Submit using Submit Current Exercise in the IDE plugin, review the file list, and confirm Submit. Alternatively, create a ZIP from the exercise folder:

SystemCommand
Windowspy -m zipfile -c submission.zip main.py check.py
macOS/Linuxpython3 -m zipfile -c submission.zip main.py check.py

Upload submission.zip below and press Submit. The listed files must be at the archive’s top level, without an enclosing folder. Include the supplied support and sample files; leave out generated outputs, .venv, and __pycache__. Read the grading feedback after submitting. The grader also uses its own inputs; passing the sample checks alone does not establish the whole contract.

Contents, Encoding, and Format

UTF-8 specifies how characters are encoded as bytes. A byte is a small unit of stored data; a character can require more than one byte. In text mode, Python decodes the file’s bytes into a string when reading and encodes strings into bytes when writing. Use UTF-8 explicitly and consistently for the course’s text files.

Files and Formats

A text document, a photograph, and a saved game can all be stored as files. A file format describes how a program should interpret the contents.

Text files contain bytes intended to be decoded as characters using an encoding. CSV and JSON add structure to that text so programs can recover fields, lists, and records. You will use both formats in this part.

Binary formats such as PNG images and ZIP archives arrange bytes for an image reader or archive tool to interpret. Reading them as ordinary text does not interpret their format. Text mode is useful for this part’s text files; other formats need tools that understand their structure.

Understand the Path

Files live in folders, also called directories. A folder can contain other folders. A path describes how to reach a file through those folders.

Suppose your course folder contains these files:

course/
practice/
main.py
practice_note.txt

The current working directory is the folder from which a running program resolves relative paths. If that directory is practice, the relative path "practice_note.txt" reaches the note. If the working directory is course, the relative path is "practice/practice_note.txt" instead. The file has stayed in the same place; the starting point has changed.

An absolute path gives the full location. The same note might be /Users/ada/course/practice/practice_note.txt on macOS or Linux, or C:\Users\Ada\course\practice\practice_note.txt on Windows. Those examples depend on a particular computer; the relative paths above work from the stated folders wherever the course folder is stored.

For the downloaded exercises, run commands from the exercise folder so the supplied relative filenames find the sample data. Files your program writes remain there after the process ends; open them in the IDE to inspect their contents. The short browser examples above create and read their temporary files during one execution.

Creating a file does not create a missing parent folder. For a local experiment using a path inside a practice folder, create that folder first.

Filename Extensions Describe an Expected Format

The extension is the suffix after the last dot in a filename, such as .txt, .csv, or .png. It conventionally indicates a format. Renaming picture.png to picture.txt changes the name while leaving the file’s bytes unchanged; it does not convert the image into text.

Your code chooses how to read a file. open(..., encoding="utf-8") attempts to decode text regardless of the extension. Likewise, a JSON parser expects JSON even when a filename ends in .csv. Check the source of a file and its documented format when choosing how to read it.

Optional: Try Different Working Directories

Use the editor and Python installation from Working in a Programming Environment. Keep main.py and these practice files in one folder. The course examples also work in the browser editor.

On your own computer, you can choose the starting point in the terminal. If the terminal is currently in course, these commands move into practice, list its files, and run the program:

cd practice
ls
python main.py

Use dir instead of ls in Windows Command Prompt, and use python3 or py if that is your Python command. cd .. moves back to the containing folder. These are terminal commands, not lines to put in a Python file.

Launching python practice/main.py from course also runs the program, but it leaves the working directory at course. An open("practice_note.txt") inside that program would then look in course, not beside main.py. The next chapter uses os.getcwd() to inspect the working directory when you need to check it.

Review how reading lines and joining strings affect whitespace and line breaks.

Check Your Understanding

  1. When does opening an existing file in "w" mode remove its old contents?
  2. Why does a with block help manage an open file?
  3. For names = ["Ada", "Grace"], how do file.write("\n".join(names)) and print("\n".join(names)) differ in their line breaks?
  4. Why can a correct relative path fail when a program is launched from another directory?
  5. What does UTF-8 tell Python, and what additional rules does a format such as CSV provide?
  6. A PNG image is renamed from picture.png to picture.txt. What changes, what stays the same, and why does renaming it not make it readable as ordinary text?