PEP 8 in Python: Write Cleaner Code

Published on: July 10, 2026
Reading time: 6 minutes
Logo do Python com o texto 'PEP 8' sobre fundo azul escuro, representando o guia de estilo da linguagem

PEP 8 in Python is the best-known style guide for writing readable and consistent code. It does not change what a program can do, but it helps teams understand, review, test, and maintain the program more easily.

This beginner guide explains naming conventions, indentation, line length, imports, whitespace, blank lines, comments, functions, classes, comparisons, exceptions, and automated formatting tools. You will also refactor a poorly formatted example into cleaner Python.

PEP 8 works best when combined with good docstrings, type hints, and automated tests.

What Is a PEP?

PEP means Python Enhancement Proposal. PEPs document standards, design ideas, processes, and information for the Python community. PEP 8 is titled “Style Guide for Python Code.”

Read the authoritative PEP 8 document. The official Python control flow tutorial also uses conventional formatting in its examples.

Why Style Matters

Code is read more often than it is written. Consistent style reduces mental effort because readers do not need to decode a new formatting system in every file.

  • Reviews focus on logic instead of formatting.
  • Names communicate purpose.
  • Diffs contain fewer unrelated changes.
  • Tools can detect common problems automatically.
  • New contributors understand the project faster.

PEP 8 is guidance, not a substitute for judgment. A project’s established convention may take priority when consistency would otherwise be broken.

Use Four Spaces for Indentation

Python uses indentation as syntax. PEP 8 recommends four spaces per indentation level.

def calculate_total(prices: list[float]) -> float:
    total = 0.0

    for price in prices:
        if price >= 0:
            total += price

    return total

Do not mix tabs and spaces. Configure your editor to insert spaces when you press Tab. The Python indentation guide explains blocks and common errors.

Limit Line Length Thoughtfully

PEP 8 traditionally recommends a maximum of 79 characters for code and 72 for long comments or docstrings. Some modern teams choose a slightly larger limit, such as 88 or 100, but the important rule is to agree and apply it consistently.

Break long function calls inside parentheses:

response = client.create_report(
    customer_id=customer_id,
    start_date=start_date,
    end_date=end_date,
    include_details=True,
)

Use a trailing comma in multi-line structures so formatters and version-control diffs remain clean.

Naming Conventions

ElementConventionExample
Variablessnake_casetotal_price
Functionssnake_caseload_report()
ClassesCapWordsInvoiceService
ConstantsUPPER_CASEMAX_RETRIES
Modulesshort snake_caseemail_utils.py

Choose names based on meaning, not data type:

# Unclear
x = 30
lst = [10, 20, 30]

# Clearer
maximum_attempts = 30
monthly_sales = [10, 20, 30]

Short names are reasonable for tiny scopes, such as x and y coordinates or i in a very small mathematical loop. Descriptive names are better in business logic.

Avoid Ambiguous Names

Single letters such as lowercase L, uppercase I, and uppercase O can resemble numbers. Avoid them as variable names.

# Avoid
l = 10
O = 0

# Prefer
line_count = 10
starting_offset = 0

Organize Imports

Place imports near the top of the file and group them in this order:

  1. Standard library imports.
  2. Third-party imports.
  3. Local application imports.
from pathlib import Path
import logging

import pandas as pd
import requests

from app.config import Settings
from app.reports import build_report

Separate groups with blank lines. Prefer one import statement per line for ordinary imports:

import os
import sys

Importing several names from the same module on one line is acceptable when it remains readable.

Avoid Wildcard Imports

# Avoid
from calculations import *

# Prefer
from calculations import calculate_tax, calculate_total

Wildcard imports hide where names came from and can overwrite existing names unexpectedly.

Whitespace Around Operators

subtotal = quantity * unit_price
total = subtotal + shipping_cost
is_valid = total >= 0 and quantity > 0

Avoid unnecessary alignment with many spaces because it creates fragile formatting:

# Avoid decorative alignment
a_long_name = 1
x           = 2

# Prefer
long_name = 1
x = 2

No Extra Spaces Inside Delimiters

# Prefer
items = ["book", "course"]
result = calculate_total(items)
record = {"name": "Maya", "active": True}

# Avoid
items = [ "book", "course" ]
result = calculate_total( items )

Function Arguments and Defaults

Do not add spaces around = in keyword arguments or unannotated default values:

send_email(recipient="[email protected]", urgent=True)


def connect(timeout=10):
    pass

With type annotations, use spaces around the default-value equals sign:

def connect(timeout: int = 10) -> None:
    pass

Use Blank Lines to Separate Ideas

At module level, surround top-level functions and classes with two blank lines. Inside a class, use one blank line between methods. Within a function, use blank lines sparingly to separate logical steps.

DEFAULT_TAX_RATE = 0.2


def calculate_tax(amount: float) -> float:
    return amount * DEFAULT_TAX_RATE


class Invoice:
    def __init__(self, subtotal: float) -> None:
        self.subtotal = subtotal

    def total(self) -> float:
        return self.subtotal + calculate_tax(self.subtotal)

Write Useful Comments

Comments should explain why a decision exists, not translate obvious code into English.

# Retry because the provider may briefly return 503 during deployment.
for attempt in range(MAX_RETRIES):
    response = send_request()

Avoid stale comments. Update or remove a comment whenever the related code changes. For public functions and classes, use docstrings rather than a block of ordinary comments.

Use Docstrings for Public Interfaces

def calculate_discount(total: float, rate: float) -> float:
    """Return the discount amount for a total and decimal rate."""
    return total * rate

PEP 257 provides docstring conventions. The Python docstrings guide covers parameters, returns, exceptions, modules, and classes.

Compare Booleans Clearly

# Prefer
if is_active:
    process_account()

if not is_deleted:
    show_account()

# Usually avoid
if is_active == True:
    process_account()

Use is None for None checks:

if result is None:
    return "No result"

Read the Python booleans guide for truth values and identity.

Use is not Instead of not … is

# Prefer
if value is not None:
    print(value)

# Avoid
if not value is None:
    print(value)

The preferred form reads like normal English and reduces ambiguity.

Catch Specific Exceptions

try:
    quantity = int(user_input)
except ValueError:
    print("Enter a whole number.")

Avoid bare except: because it catches system-exiting exceptions and makes debugging difficult. Catch the narrowest useful exception and keep the protected block small.

try:
    data = file_path.read_text(encoding="utf-8")
except FileNotFoundError:
    logger.error("Missing file: %s", file_path)
except PermissionError:
    logger.error("Permission denied: %s", file_path)

Avoid Mutable Default Arguments

This is a correctness rule commonly taught alongside style:

# Avoid
def add_item(item, items=[]):
    items.append(item)
    return items


# Prefer
def add_item(item, items=None):
    if items is None:
        items = []

    items.append(item)
    return items

The default list in the first function is reused across calls.

Keep Functions Focused

A function should have one clear responsibility. Large functions are harder to name, test, and understand.

def load_sales(path: Path) -> list[dict]:
    """Load sales records from a file."""
    ...


def validate_sales(records: list[dict]) -> list[dict]:
    """Return valid sales records."""
    ...


def summarize_sales(records: list[dict]) -> dict:
    """Aggregate sales totals by category."""
    ...

This structure works well with automated tests.

Refactoring a Poorly Styled Example

Here is difficult-to-read code:

import os,sys
TAX=.2
def calc(x,y=True):
 if y==True:return x+(x*TAX)
 else:return x

A cleaner version:

TAX_RATE = 0.2


def calculate_total(
    subtotal: float,
    include_tax: bool = True,
) -> float:
    """Return the subtotal with optional tax."""
    if not include_tax:
        return subtotal

    tax = subtotal * TAX_RATE
    return subtotal + tax

The revised code removes unused imports, uses descriptive names, follows indentation rules, adds type hints and a docstring, and avoids comparing a boolean directly with True.

Formatting and Linting Tools

Automated tools reduce debates and catch problems early:

  • Black: opinionated automatic formatter.
  • Ruff: fast linter and formatter with many rule sets.
  • Flake8: established linting tool.
  • isort: import organizer.
  • Pylint: detailed static analysis.

A formatter changes layout. A linter reports style, quality, and possible correctness problems. Many projects use both.

python -m pip install ruff
ruff check .
ruff format .

Review changes in version control rather than applying tools blindly to a large legacy project.

Configure the Project

Place tool settings in pyproject.toml so every developer and CI job uses the same rules:

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "I"]

Gradually add stricter rules as the codebase becomes consistent.

Style in Existing Projects

Do not reformat an entire codebase during an unrelated bug fix. Large formatting-only diffs hide meaningful changes and create merge conflicts. Prefer:

  • formatting new code;
  • cleaning files being actively changed;
  • a separate, agreed formatting migration;
  • automated checks to prevent new inconsistencies.

Common Mistakes

  • Mixing tabs and spaces.
  • Using unclear one-letter names in business logic.
  • Putting several imports on one line.
  • Using wildcard imports.
  • Writing comments that repeat the code.
  • Comparing booleans with True or False unnecessarily.
  • Catching every exception with a bare except.
  • Applying a formatter without reviewing project conventions.
  • Treating every PEP 8 recommendation as more important than clarity.

Frequently Asked Questions

Is PEP 8 mandatory?

Python does not require it for execution. Teams adopt it to improve consistency and readability.

Does Black follow every PEP 8 rule?

Black follows a consistent style influenced by PEP 8 but makes its own formatting decisions, including a default line length.

Should all lines be shorter than 79 characters?

PEP 8 recommends 79 for code, but projects may choose another documented limit. Avoid excessively long lines regardless of the exact number.

Can I ignore a style rule?

Yes, when following it would reduce clarity or conflict with a well-established project convention. Make exceptions deliberate and limited.

Conclusion

PEP 8 helps Python code look familiar across projects. Use four-space indentation, meaningful names, organized imports, sensible line breaks, focused functions, specific exceptions, and comments that explain decisions.

Automate the mechanical parts with a formatter and linter, but keep human judgment at the center. The goal is not perfect compliance—it is code that other people can understand and safely change.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Pessoa programando em um notebook, vista de trás, com código desfocado exibido na tela em um fundo escuro
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Docstrings: Document Code Clearly

    Learn Python docstrings for functions, classes, methods, modules, parameters, returns, exceptions, examples, pydoc, conventions, and documentation tools.

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026
    Configuração de logs em aplicações Python usando logging
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Logging: A Complete Beginner’s Guide

    Learn Python logging from scratch: levels, files, formatters, exceptions, handlers, rotation, and practical examples for reliable applications.

    Ler mais

    Tempo de leitura: 10 minutos
    10/07/2026
    Dicas para otimizar scripts Python lentos
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Why Is Your Python Script Slow? Fixes

    Learn why Python scripts run slowly and how to fix it: use built-ins, list comprehensions, sets, generators, NumPy, cProfile profiling,

    Ler mais

    Tempo de leitura: 3 minutos
    03/06/2026
    Problemas de travamento com threading em scripts Python
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Threading: Stop Your Script From Freezing

    Learn Python threading: why scripts freeze, create and start threads, use join(), avoid race conditions with Lock, and use ThreadPoolExecutor

    Ler mais

    Tempo de leitura: 4 minutos
    03/06/2026
    Criação de cliente TCP simples usando Python
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Build a Simple Python TCP Client

    Build a simple Python TCP client using the socket module: connect to a server, send and receive bytes, handle errors,

    Ler mais

    Tempo de leitura: 3 minutos
    03/06/2026
    Criação de hashes seguros para senhas usando Python
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Password Hashing with bcrypt

    Learn Python password hashing with bcrypt: generate secure hashes, verify passwords, understand salt, and build a complete authentication system.

    Ler mais

    Tempo de leitura: 4 minutos
    03/06/2026