[go: up one dir, main page]

Breaking up long lines of code in Python

Share
Copied to clipboard.
Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
6 min. read 3 min. video Python 3.10—3.14

When it comes to code readability, whitespace is your friend.

Whitespace around operators

Compare this:

a**2+b**2+c**2

To this:

a**2 + b**2 + c**2

I find that second one more readable because the operations we're performing are more obvious (as is the order of operations).

Too much whitespace can hurt readability though:

a ** 2 + b ** 2 + c ** 2

This seems like a step backward because we've lost those three groups we had before.

With both typography and visual design, more whitespace isn't always better.

Using line breaks wisely

Adding extra line breaks to code can sometimes improve readability.

For example, I almost always break my comprehensions over multiple lines in Python. I prefer this:

html_files = [
    p.name
    for p in paths
    if p.suffix == ".html"
]

Instead of this:

html_files = [p.name for p in paths if p.suffix == ".html"]

That first comprehension is perfectly valid Python code thanks to Python's allowance for implicit line continuation. If your code is wrapped in a bracket, brace, or parentheses, you can add as many line breaks as you'd like and Python will still treat the code as a single "line".

When breaking up code over multiple lines, focus on breaking at logical component boundaries rather than at arbitrary line lengths. In that list comprehension above, we've broken the code before each for and if clause, making the logical structure obvious

Use implicit line continuation as needed

Sometimes I'll even add parentheses for the sake of an implicit line continuation:

fruits = ["grapes", "apples", "blueberries", "watermelon", "pears"]

print("I like", " and ".join(sorted(fruits)), "but I only like certain pears")

That print line could technically fit within a reasonable line length, but it's quite dense and hard to parse. Adding parentheses allows us to break it up for better readability:

fruits = ["grapes", "apples", "blueberries", "watermelon", "pears"]

print(
    "I like",
    " and ".join(sorted(fruits)),
    "but I only like certain types of pears"
)

Now the distinct arguments to print are much more obvious.

Chained method calls

When chaining methods together, you may want to add parentheses around the entire expression to allow line breaks between each method call:

books = (Book.objects
             .filter(author__in=favorite_authors)
             .select_related('author', 'publisher')
             .order_by('title'))

Here we've broken up each method call onto its own line, making it clear what operations we're performing in sequence.

Of course, how you wrap your code is up to the style your project uses.

I usually prefer this wrapping style:

books = (
    Book.objects.filter(author__in=favorite_authors)
    .select_related("author", "publisher")
    .order_by("title")
)

Dictionary literals

Long dictionaries can be broken up with each key-value pair on its own line:

MONTHS = {
    'January': 1,
    'February': 2,
    'March': 3,
    'April': 4,
    'May': 5,
    'June': 6,
    'July': 7,
    'August': 8,
    'September': 9,
    'October': 10,
    'November': 11,
    'December': 12,
}

I sometimes even wrap short dictionaries and lists this way as well, for the sake of readability.

For example that fruits list above:

fruits = ["grapes", "apples", "blueberries", "watermelon", "pears"]

Could have been written like this:

fruits = [
    "grapes",
    "apples",
    "blueberries",
    "watermelon",
    "pears",
]

I find this slightly more readable. Note the trailing comma in the above examples as well.

Blank lines

PEP8 recommends 2 blank lines between top-level classes and functions and 1 blank line between methods within a class:

import os


class set_env_var:
    def __init__(self, var_name, new_value):
        self.var_name = var_name
        self.new_value = new_value

    def __enter__(self):
        self.original_value = os.environ.get(self.var_name)
        os.environ[self.var_name] = self.new_value

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.original_value is None:
            del os.environ[self.var_name]
        else:
            os.environ[self.var_name] = self.original_value


def process_environment():
    """Process environment variables."""
    return os.environ.get('PATH')

Note the 2 blank lines after the imports, 2 blank lines between the class and the function, and 1 blank line between each method within the class

Although, some Python developers prefer to use only 1 blank line between top-level classes and functions.

You may also find one or more blank lines between blocks of code useful, either at the global scope:

import subprocess
import sys

process = subprocess.run(
    ["git", "branch", "--format=%(refname:short)"],
    capture_output=True,
    text=True,
)
lines = process.stdout.splitlines()

# Try to determine the default branch name
if "main" in lines:
    print("main")
elif "master" in lines:
    print("master")
elif "trunk" in lines:
    print("trunk")
else:
    sys.exit("Default branch is unknown")

Here blank lines visually separate the different blocks of this script: imports, subprocess execution, and conditional logic

Or within a function:

TODO show better example...

def calculate_average(numbers):
    """Calculate the average of a list of numbers."""
    if not numbers:
        return 0

    total = sum(numbers)
    count = len(numbers)

    average = total / count
    return round(average, 2)

The blank lines here separate the different logical phases: validation, calculation, and result formatting

Usually when I find myself tempted to put a blank line between code blocks, I ask myself whether one or more of the code blocks should live in its own function.

Some Python developers prefer to use blank lines very liberally. For example, this code has many short blocks of code separated by blank lines:

TODO show better example...

def load_usage_data(csv_path):
    """Load SDGE Green Button 15-minute interval data"""

    print(f"Loading data from {csv_path}...")

    df = pd.read_csv(csv_path)
    df['datetime'] = pd.to_datetime(
        df['Date'] + ' ' + df['Start Time'],
        format='%m/%d/%Y %I:%M %p'
    )

    df['consumption_kwh'] = df['Consumption'].astype(float)

    print(f"Loaded {len(df)} records")
    print(f"Date range: {df['datetime'].min()} to {df['datetime'].max()}")

    return df

I would probably prefer to write that same code like this:

def load_usage_data(csv_path):
    """Load SDGE Green Button 15-minute interval data"""

    print(f"Loading data from {csv_path}...")
    df = pd.read_csv(csv_path)
    df['datetime'] = pd.to_datetime(
        df['Date'] + ' ' + df['Start Time'],
        format='%m/%d/%Y %I:%M %p'
    )

    df['consumption_kwh'] = df['Consumption'].astype(float)

    print(f"Loaded {len(df)} records")
    print(f"Date range: {df['datetime'].min()} to {df['datetime'].max()}")
    return df

There are still blank lines in this code, but fewer of them

The whitespace is for us, not for Python

Python mostly doesn't care about whitespace.

That might seem like a strange thing to say, since Python cares about indentation and Python uses line breaks to end a line of code. But most whitespace is for us more than it's for Python.

Python doesn't care about:

Python doesn't care much about our use of whitespace, but humans do.

Make your code more readable to other humans by using whitespace wisely.

Whitespace is all about visual grouping

Where you use whitespace depends on what you want to visually separate or group together in your code.

The moment of peak readability is the moment just after you write a line of code. Your code will be far less readable to you one day, one week, and one month after you've written it.

When cleaning up your code style, remember to ask yourself "could I add or remove whitespace to improve this code's readability?" Make sure to craft your code carefully as you write it because your future self will have a much more difficult time cleaning it up than you will right now.

Whitespace is your friend. Use it wisely.

5 Keys to Python Success 🔑

Sign up for my 5 day email course and learn essential concepts that introductory courses often overlook!