Tiny Python refactorings: try/except - with

Using try/except and with to better organize the flow of a program.

Scenario: Execute a command based on an id, if this id is present in the given file, otherwise execute a different command and save the resulting id in a file. This will be picked up later in a future execution.

A naive implementation

import subprocess


def do_stuff():
    """
    Execute a command based on an id,
    if a certain id is present in the given file.
    """
    file = open("last_id.txt")
    id = file.readline()
    if id:
        subprocess.run(["echo", id])
        file.close()
        return
    file.close()
    """
    Execute a command and save the resulting id in a file. 
    This will be picked up later in a future execution.
    """
    completed_process = subprocess.run(
        ["echo", "1673"], stdout=subprocess.PIPE, universal_newlines=True
    )
    file = open("last_id.txt", "w")
    file.write(f"{completed_process.stdout}\n")
    file.close()


do_stuff()

The problem with this is code is that it throws because it can't find the file on the first execution:

FileNotFoundError: [Errno 2] No such file or directory: 'last_id.txt'

To solve this problem, let's wrap the first block of code in a try block, and the second part in except:

import subprocess


def do_stuff():
    try:
        file = open("last_id.txt")
        id = file.readline()
        if id:
            subprocess.run(["echo", id])
            file.close()
        file.close()
    except FileNotFoundError:
        completed_process = subprocess.run(
            ["echo", "1673"], stdout=subprocess.PIPE, universal_newlines=True
        )
        file = open("last_id.txt", "w")
        file.write(f"{completed_process.stdout}\n")
        file.close()


do_stuff()

This ensures that:

  • the program doesn't halt
  • if the file doesn't exist yet, it's created on first run

You can read about try/except here.

However, this tiny refactoring does not solve completely the problem, because if the first subprocess.run() fails, we're still left with an open file descriptor hanging around.

This can be reproduced by running python with -X dev (thanks to Sviatoslav for this trick), and by misspelling the command in the call to .run():

        if id:
            subprocess.run(["eco", id])
            file.close()
            return
        file.close()

The result:

ResourceWarning: unclosed file <_io.TextIOWrapper name='last_id.txt' mode='r' encoding='UTF-8'>
  file = open("last_id.txt", "w")

To make the code more robust, we can use a with context manager, which is able to close files for us:

import subprocess


def do_stuff():
    try:
        with open("last_id.txt") as file:
            id = file.readline()
            if id:
                subprocess.run(["echo", id])
                return
    except FileNotFoundError:
        completed_process = subprocess.run(
            ["echo", "1673"], stdout=subprocess.PIPE, universal_newlines=True
        )
        with open("last_id.txt", "w") as file:
            file.write(f"{completed_process.stdout}\n")


do_stuff()

Thanks to Harry and Sviatoslav for catching a couple of bugs in my example.

Valentino Gagliardi

Hi! I’m Valentino! Educator and consultant, I help people learning to code with on-site and remote workshops. Looking for JavaScript and Python training? Let’s get in touch!

More from the blog: