Code Formatting & Layout

Well-formatted code is easier to read, easier to review, and easier to maintain. Python's PEP 8 style guide provides clear rules for formatting, and modern tools can enforce them automatically.

Indentation: 4 Spaces, Always

Python uses indentation to define code blocks. The standard is 4 spaces per level — never tabs:

# Correct: 4 spaces
def greet(name):
    if name:
        message = f"Hello, {name}!"
        print(message)
    else:
        print("Hello, stranger!")

# Wrong: tabs, 2 spaces, or mixed
def greet(name):
  if name:      # 2 spaces — not standard
      print(name)  # mixed indentation — will cause errors

Why not tabs? Tabs display differently depending on the editor (2, 4, or 8 spaces wide). Spaces look the same everywhere. Python 3 actually forbids mixing tabs and spaces within the same block.

Python Playground
Output
Click "Run" to execute your code

Continuation Indentation

When a function call or definition spans multiple lines, align arguments or use a hanging indent:

# Option 1: Align with opening delimiter
result = some_function(arg_one, arg_two,
                       arg_three, arg_four)

# Option 2: Hanging indent (4 spaces from the base)
result = some_function(
    arg_one, arg_two,
    arg_three, arg_four,
)

# Option 3: Hanging indent for function definitions
def long_function_name(
    param_one, param_two,
    param_three, param_four,
):
    print(param_one)

Line Length

PEP 8 recommends a maximum of 79 characters per line. In practice, many projects use 99 or 120 as a more comfortable limit:

Standard Line Limit Common Usage
PEP 8 strict 79 Standard library, open source
Black default 88 Projects using Black formatter
Common relaxed 99-120 Internal/company projects

Breaking Long Lines

When a line is too long, you have two options:

# Option 1: Implicit continuation with parentheses (preferred)
total = (
    first_variable
    + second_variable
    + third_variable
)

# Also works with function calls
result = some_function(
    argument_one,
    argument_two,
    argument_three,
)

# Option 2: Backslash continuation (less preferred)
total = first_variable \
    + second_variable \
    + third_variable

Implicit continuation with parentheses is preferred because backslashes are fragile — a space after the backslash silently breaks the continuation.

Python Playground
Output
Click "Run" to execute your code

Blank Lines

Blank lines separate logical sections of code and improve readability:

  • 2 blank lines before and after top-level definitions (functions, classes)
  • 1 blank line between methods inside a class
  • 1 blank line to separate logical sections within a function (sparingly)
import os


class FileProcessor:
    """Process files from a directory."""

    def __init__(self, directory):
        self.directory = directory

    def list_files(self):
        return os.listdir(self.directory)

    def process(self):
        for f in self.list_files():
            self._handle_file(f)

    def _handle_file(self, filename):
        print(f"Processing: {filename}")


def helper_function():
    """A standalone function, separated by 2 blank lines."""
    pass

Import Ordering

Imports should appear at the top of the file and be grouped in a specific order, with a blank line between each group:

  1. Standard library imports
  2. Third-party imports
  3. Local application imports
# Group 1: Standard library
import os
import sys
from pathlib import Path
from collections import defaultdict

# Group 2: Third-party packages
import requests
from flask import Flask, jsonify
from sqlalchemy import create_engine

# Group 3: Local imports
from myapp.config import Settings
from myapp.models import User

Import Style

Prefer import module for top-level modules and from module import name when you need specific items:

# Good: import the module
import os
import json

# Good: import specific names when it improves readability
from pathlib import Path
from collections import defaultdict, OrderedDict
from typing import Optional, List

# Avoid: wildcard imports (pollutes namespace, hides where names come from)
from os import *          # Bad
from typing import *      # Bad

# Avoid: importing too many names from one module
from os import path, getcwd, listdir, mkdir, remove, rename  # Consider: import os

Tip: The isort tool automatically sorts and groups your imports according to these rules. Most formatters like Black integrate with it.

Python Playground
Output
Click "Run" to execute your code

Whitespace Rules

Around Operators

Use a single space around assignment and comparison operators:

# Good
x = 5
y = x + 3
is_valid = x > 0 and y < 10

# Bad
x=5
y = x+3
is_valid = x>0 and y<10

Exception: Default Arguments and Keyword Arguments

Don't use spaces around = in function parameters:

# Good
def connect(host="localhost", port=8080, timeout=30):
    pass

connect(host="example.com", port=443)

# Bad
def connect(host = "localhost", port = 8080):
    pass

After Commas

Always put a space after commas, not before:

# Good
numbers = [1, 2, 3, 4, 5]
result = calculate(x, y, z)

# Bad
numbers = [1,2,3,4,5]
numbers = [1 , 2 , 3]

Inside Brackets

Don't add spaces immediately inside parentheses, brackets, or braces:

# Good
numbers = [1, 2, 3]
data = {"key": "value"}
result = func(arg)

# Bad
numbers = [ 1, 2, 3 ]
data = { "key": "value" }
result = func( arg )

Colons in Slices

No spaces around colons in slices (but spaces around colons in dicts):

# Good
numbers[1:3]
numbers[::2]
data = {"key": "value"}

# Bad
numbers[1 : 3]
numbers[ ::2 ]

Python Playground
Output
Click "Run" to execute your code

Trailing Commas

Use trailing commas in multi-line data structures. This makes diffs cleaner when items are added:

# Good: trailing comma
fruits = [
    "apple",
    "banana",
    "cherry",  # <-- trailing comma
]

# Without trailing comma, adding "date" changes two lines in the diff
# With trailing comma, adding "date" only changes one line

Auto-Formatters

Instead of manually enforcing all these rules, let a tool do it. The three most popular Python formatters:

Black

The most popular formatter. It's opinionated and has almost no configuration — which is the point. "Any color you like, as long as it's black."

# Install
pip install black

# Format a file
black my_script.py

# Format an entire project
black src/

Black uses 88 characters as its default line length and makes all formatting decisions for you. This eliminates style debates in code reviews.

autopep8

A more conservative formatter that only fixes PEP 8 violations:

pip install autopep8
autopep8 --in-place my_script.py

yapf (Yet Another Python Formatter)

Google's formatter. More configurable than Black but less opinionated:

pip install yapf
yapf --in-place my_script.py

Which One to Choose?

Formatter Philosophy Config Line Length
Black Opinionated, minimal config Almost none 88
autopep8 Fix PEP 8 violations only Moderate 79
yapf Configurable, reformats all code Extensive Configurable

Recommendation: Start with Black. Its lack of configuration means your entire team writes identically formatted code with zero debate. Pair it with isort for import sorting.

Putting It All Together

Here's a well-formatted Python file that follows all the conventions:

"""User management utilities.

Provides functions for creating, validating, and formatting user data.
"""

import re
from datetime import datetime
from typing import Optional

from myapp.database import get_connection
from myapp.exceptions import ValidationError


MAX_USERNAME_LENGTH = 30
MIN_PASSWORD_LENGTH = 8


class User:
    """Represents a registered user."""

    def __init__(self, username: str, email: str):
        self.username = username
        self.email = email
        self.created_at = datetime.now()

    def display_name(self) -> str:
        return self.username.title()


def validate_username(username: str) -> bool:
    """Check if a username meets requirements."""
    if not username:
        return False

    if len(username) > MAX_USERNAME_LENGTH:
        return False

    return bool(re.match(r"^[a-zA-Z0-9_]+$", username))


def create_user(
    username: str,
    email: str,
    password: Optional[str] = None,
) -> User:
    """Create a new user after validation."""
    if not validate_username(username):
        raise ValidationError(f"Invalid username: {username}")

    return User(username, email)

This example demonstrates proper indentation, blank lines, import ordering, whitespace, trailing commas, and line length management.