skip to content
Llego.dev

Write Cleaner Python: Mastering the PEP 8 Style Guide

/ 8 min read

Updated:

PEP 8 is Python’s official style guide, providing a set of rules and coding conventions to standardize code across different Python projects and developers. It covers topics including naming conventions, code layout, whitespace usage, programming practices, and documentation. Following PEP 8 guidelines helps create Python code that is professional, simple, consistent, and easy to understand. The benefits of PEP 8-compliant code include:

  • Improved readability and maintainability of code.
  • Consistent styling across developers and teams.
  • Professional look and feel of code.
  • Easy to understand logic and program flow.
  • Helps avoid common errors and bugs.
  • Promotes good programming practices.
  • Enables better collaboration between developers.

This guide will break down the key components of PEP 8, providing actionable tips and corrected Python code examples for writing clean code that adheres to its standards. Both novice and experienced Python programmers can use this as a practical reference when writing new code or refactoring existing code. The intended audience for this article is Python developers of all skill levels who want to write more readable, maintainable, and collaborative code.

Naming Conventions

PEP 8 contains specific guidelines for naming variables, functions, classes, modules, etc. Using clear, descriptive, and consistent naming helps improve code understandability.

Use lowercase with underscores

Function, variable, and module names should be in all lowercase, using underscores to separate words:

# Correct conventions
my_variable = 'Hello'
def calculate_sum(numbers):
pass # Added pass statement to make the function valid
import helper_module

Class names should be in CamelCase format:

# Class name uses CamelCase
class MyClassName:
pass

Avoid single letter names

Single letter variables like ‘x’ or ‘y’ can be confusing. Use more descriptive names:

# Avoid
x = 1
y = 2
# Preferred
number_of_items = 1
item_counter = 2

Name booleans and constants appropriately

Booleans should be named using ‘is’ or ‘has’ prefixes. Constants should be all caps with underscores:

# Booleans
is_active = True
has_permissions = False
# Constants
MAX_SIZE = 100
CONNECTION_TIMEOUT = 50

Use verbs for function names

Function names should clearly describe the action being performed and use a verb to start:

# Good function names
def get_user_data():
pass
def calculate_average(numbers):
pass

Code Layout and Whitespace

Proper spacing, indentation, and placement of code blocks improves code organization and visual appearance.

Indentation

  • Use 4 spaces per indentation level (no tabs).
  • Indent after function definitions, loops, if statements etc.
  • Continued lines should align vertically using the same indentation or using a hanging indent.
# Aligned indentation
def process_user(user):
if user.is_active():
print('Active user found')
# Removed undefined functions for clarity
# send_notification()
# update_status('Active')
pass
for item in user.list_of_items:
# Removed undefined functions for clarity
# process_item(item)
# save_changes()
pass

Line length

  • Limit lines to 79 characters for code and 72 characters for comments and docstrings.
  • Use parentheses, hanging indents, and line continuation for long lines.
# Use parentheses
def long_function_call_with_many_args(arg1, arg2, arg3,
arg4, arg5):
pass
# Hanging indents
long_string = ('This is a very long string that goes over '
'the character limit')
# Line continuation
long_calculation = (some_value + some_other_value -
some_condition)

Whitespace

  • Surround operators and assignments with spaces for readability.
  • Do not use extraneous whitespace inside parentheses, brackets, or braces.
# Spaces around operators
x = 1 + 2
value = (3 * 4) - 5
# No extra whitespace
my_list = [1, 2, 3]
my_dict = {'key': 'value'}
# Spaces separating items
if x == 1 and y == 2:
pass

Blank lines

  • Use blank lines sparingly to separate logical sections of code.
  • Two blank lines between top-level function and class definitions.
  • One blank line between method definitions in a class.
  • No blank lines inside brackets, braces, or parentheses.
# Logical blank line spacing
def process_data():
data = get_data()
# Blank line
results = analyze(data)
return results
class MyClass:
def __init__(self):
pass
# Blank line
def method1(self):
pass
def method2(self):
pass

Imports

Imports should be organized and located at the top of the file below module documentation.

Group imports

Imports should be grouped in the following order:

  1. Standard library imports
  2. Third-party package imports
  3. Local application imports

Each grouping should be separated by one blank line:

# Standard library
import os
import sys
import json
# Third-party
import numpy as np
import pandas as pd
# Local imports
from . import helper
from .utils.converters import convert_to_float

Avoid relative package imports (generally)

While relative imports have their use cases within a package, PEP 8 generally recommends absolute imports for better clarity and to avoid potential issues with the import path. For imports within the same package, explicit relative imports are acceptable (e.g., from .module import something).

Limit line length

Break long import statements across multiple lines, ideally limiting to 79 characters. Use parentheses and hanging indents when needed:

# Long imports
from third_party_lib.specific_package import (
VeryLongModuleName,
AnotherVeryLongName,
)
from my_local_package.utils.modules import (
my_module,
my_other_module,
helper_functions,
)

Programming Practices

PEP 8 contains several guidelines for best practices when coding in Python:

Avoid extraneous code

Remove any code that does not get used - old debug statements, unnecessary comments, unused variables, or imports, etc. They add clutter and increase file size needlessly.

Use meaningful comments

Comments should explain complex code or algorithms. Avoid extraneous comments that just restate the code. Strive for comments that explain the why rather than just the what.

# Good comments
# This converts the dataframe columns to numeric using partial function application
# to ensure consistent data types for analysis.
# cols_to_numeric = partial(convert_cols, df=dataframe, cols=['col1', 'col2']) # Requires import and definition of partial and convert_cols
# Bad comments
x = x + 1 # Increment x by 1

Modularize code into functions

Break code into logical chunks using functions for better organization. Functions should ideally do one thing and do it well.

Avoid deeply nested control flows

Nested if statements, loops, and comprehensions can make code hard to read. Use functions to encapsulate the logic or consider alternative control flow structures.

Use context managers

Use context managers like ‘with’ for resource handling instead of try/finally blocks. This ensures resources are properly cleaned up even if errors occur.

# Context manager
with open('file.txt') as f:
data = f.read()
# Without context manager
f = open('file.txt')
try:
data = f.read()
finally:
f.close()

Prefer enumerate over range when you need the index

Use enumerate() to get index values when iterating through a sequence, as it’s more Pythonic and readable.

list_data = ["a", "b", "c"]
for i in range(len(list_data)):
print(i, list_data[i])
# Better
for i, val in enumerate(list_data):
print(i, val)

Documentation

Proper documentation makes code easier to use and understand. PEP 8 recommends using:

Docstrings

Docstrings provide help text and usage information. Use triple double-quotes and include Args/Returns/Raises sections for public modules, functions, classes, and methods.

import math
def calculate_area(radius):
"""Calculate the area of a circle.
Args:
radius (int or float): The radius of the circle.
Returns:
float: The calculated circle area.
"""
area = math.pi * radius**2
return area

Comments

Comments improve understandability and provide explanation for specific lines or sections of code. Use them sparingly inside functions/classes to explain non-obvious logic.

def process_data(data):
# Ensure data is sorted by timestamp before processing
data.sort(key=lambda x: x['timestamp'])
# ... rest of the processing logic
pass

Version Docstrings

While not a strict PEP 8 requirement, including a module-level docstring with version information, author, and creation date is a good practice for maintainability and tracking.

"""my_module.py
Performs X task.
Version: 1.0
Author: Mark Anthony Llego
Created: 2021-01-10
"""

Tools for PEP 8 Compliance

Linters

Using a linter like flake8 (which includes pycodestyle, pyflakes, and mccabe) will automatically check your code and catch PEP 8 issues/violations. Configure your editor or CI/CD pipeline to run a linter regularly to enforce compliance.

Auto-formatters

Tools like Black and autopep8 can automatically reformat your code to conform to PEP 8 standards. Black is more opinionated and makes fewer configuration choices, leading to consistent formatting across projects.

Conclusion

PEP 8 contains comprehensive style guidelines for writing clean, readable, and professional Python code. Following its conventions improves collaboration between developers working on shared codebases. This guide provided actionable tips and examples for writing PEP 8-compliant code covering topics like naming, whitespace, imports, practices, and documentation. Writing high-quality Python code using these standards ensures code maintainability, consistency, and better teamwork.

Example Code

Here is an example Python script demonstrating various PEP 8 conventions:

"""
example.py
Simple script with PEP 8 examples.
Author: Mark Anthony Llego
Created: 2021-01-10
"""
# Standard library
import json
import math
import sys
# Third party
import numpy as np
# Local application
from mypackage.utils import helper
# CONSTANTS
PI = 3.141592653
# Function with docstring
def calculate_area(radius):
"""Calculate the area of a circle.
Args:
radius (int or float): The circle radius.
Returns:
float: The circle area.
"""
area = PI * radius**2
return area
# Class with docstring
class Circle:
"""Class to represent a geometric circle."""
def __init__(self, radius):
"""Initialize a Circle object."""
self.radius = radius
def area(self):
"""Compute the circle's area."""
return calculate_area(self.radius)
def main():
"""Main function of the script."""
# Open file using a context manager
try:
with open('data.json') as f:
data = json.load(f)
print(data)
except FileNotFoundError:
print("Error: data.json not found.")
# Create instance of the Circle class
c = Circle(5)
print(f'Area: {c.area():.2f}')
if __name__ == '__main__':
main()

This example applies several PEP 8 guidelines discussed in this guide.