PEP 8 - Python Style Guide

Table of Contents


What is PEP 8?

PEP 8 (Python Enhancement Proposal 8) is the official style guide for Python code.

Purpose: - 📖 Improve code readability - 🔄 Ensure consistency across projects - 🛠️ Enhance maintainability

Key Principle: "Code is read much more often than it is written"


Naming Conventions

Overview Table

Element Convention Example
Variables snake_case user_name, total_count
Functions snake_case get_data(), calculate_total()
Classes PascalCase UserProfile, DatabaseConnection
Constants UPPER_SNAKE_CASE MAX_SIZE, API_KEY
Modules lowercase utils, mymodule
Packages lowercase numpy, requests
Private _leading_underscore _internal_var, _helper()
Protected _single_underscore _internal_method
Name Mangling __double_underscore __private_var

Code Layout

Indentation

Always use 4 spaces per indentation level.

# Good - 4 spaces
def my_function():
    if condition:
        do_something()
        do_another_thing()
    return result

# Bad - 2 spaces (not PEP 8)
def my_function():
  if condition:
    do_something()
  return result

# Bad - tabs (never use tabs)
def my_function():
    if condition:
        do_something()

Line Length

Maximum line lengths: - 79 characters for code - 72 characters for comments and docstrings

Breaking lines:

# Good - implicit line joining (preferred)
total = (first_variable + second_variable
         + third_variable + fourth_variable)

# Good - backslash continuation (use sparingly)
total = first_variable + second_variable \
        + third_variable + fourth_variable

# Good - function arguments
result = my_long_function_name(
    argument1, argument2,
    argument3, argument4
)

Blank Lines

Use blank lines to organize code logically.

# 2 blank lines before top-level functions and classes
import os
import sys


def top_level_function():
    """Top-level function."""
    pass


class MyClass:
    """My class."""

    # 1 blank line between methods
    def __init__(self):
        """Initialize."""
        self.value = 0

    def method_one(self):
        """First method."""
        pass

    def method_two(self):
        """Second method."""
        pass


class AnotherClass:
    """Another class."""
    pass

Inside functions:

def process_data(data):
    """Process data with logical grouping."""
    # Group 1: Input validation
    if not data:
        return None

    # Group 2: Data processing
    cleaned_data = clean(data)
    processed_data = transform(cleaned_data)

    # Group 3: Return result
    return processed_data

Import Order

Organize imports in three groups with blank lines between:

# 1. Standard library imports
import os
import sys
from datetime import datetime

# 2. Third-party imports
import numpy as np
import requests
from flask import Flask, render_template

# 3. Local application imports
from myapp.utils import helper_function
from myapp.models import User, Database
from myapp.config import API_KEY

Import Guidelines:

# Good - one import per line (standard library)
import os
import sys
import json

# Good - multiple items from same module
from datetime import datetime, timedelta, timezone

# Good - aliases (common conventions)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Avoid - wildcard imports
from module import *  # Bad - unclear what's imported

# Good - explicit imports
from module import function1, function2, Class1

Whitespace and Formatting

Spaces Around Operators

# Good - spaces around operators
x = 1 + 2
y = x * 3 - 4
result = (x + y) / 2

# Good - no space for high-priority operators
hypot = x*x + y*y
c = (a + b) * (a - b)

# Good - function arguments
def function(arg1, arg2, arg3):
    pass

# Good - function calls
result = function(1, 2, 3)

# Bad - inconsistent spacing
x=1+2
y = x*3-4
result=function( 1,2 ,3 )

No Spaces Inside Brackets

# Good
spam(ham[1], {eggs: 2})
dict['key'] = list[index]

# Bad
spam( ham[ 1 ], { eggs: 2 } )
dict ['key'] = list [index]

No Trailing Whitespace

# Good
x = 1
y = 2

# Bad (spaces after 1 and 2)
x = 1  
y = 2  

Comments and Docstrings

Inline Comments

# Good - explain why, not what
x = x + 1  # Compensate for border

# Bad - states the obvious
x = x + 1  # Increment x

# Good - useful context
if i & (i - 1) == 0:  # Check if i is power of 2
    pass

Guidelines: - Use sparingly - Start with # followed by single space - Should be on separate line when possible - Explain why, not what

Block Comments

# Good - explain complex logic
# Calculate the trajectory using the following formula:
# trajectory = initial_velocity * time + 0.5 * acceleration * time^2
# This accounts for both initial velocity and constant acceleration
trajectory = v0 * t + 0.5 * a * t**2

Docstrings

Module docstring:

"""
mymodule - Utility functions for data processing.

This module provides functions for cleaning, transforming,
and validating data from various sources.
"""

Function docstring:

def add_numbers(a, b):
    """
    Add two numbers and return the result.

    Args:
        a (int or float): First number
        b (int or float): Second number

    Returns:
        int or float: Sum of a and b

    Raises:
        TypeError: If arguments are not numeric

    Example:
        >>> add_numbers(2, 3)
        5
    """
    return a + b

Class docstring:

class BankAccount:
    """
    A bank account with basic operations.

    Attributes:
        balance (float): Current account balance
        owner (str): Account owner name

    Methods:
        deposit(amount): Add money to account
        withdraw(amount): Remove money from account
    """

    def __init__(self, owner, balance=0):
        """
        Initialize bank account.

        Args:
            owner (str): Account owner name
            balance (float, optional): Initial balance. Defaults to 0.
        """
        self.owner = owner
        self.balance = balance

Best Practices

String Formatting

name = "Alice"
age = 30
score = 95.5

# Best - f-strings (Python 3.6+)
message = f"Hello {name}, you are {age} years old"
formatted = f"Score: {score:.2f}"  # 2 decimal places

# Good - .format() method
message = "Hello {}, you are {} years old".format(name, age)
message = "Hello {name}, you are {age} years old".format(name=name, age=age)

# Avoid - % formatting (outdated)
message = "Hello %s, you are %d years old" % (name, age)

# Avoid - string concatenation
message = "Hello " + name + ", you are " + str(age) + " years old"

Comparisons

# Good - use 'is' for None, True, False
if value is None:
    pass

if flag is True:  # Or just: if flag:
    pass

if result is not None:
    pass

# Bad - don't use == for None, True, False
if value == None:  # Wrong!
    pass

# Good - implicit boolean check
if items:  # Check if list is not empty
    pass

if not items:  # Check if list is empty
    pass

# Bad - explicit comparison
if len(items) > 0:  # Unnecessary
    pass

List/Dict Comprehensions

# Good - simple comprehension
squares = [x**2 for x in range(10)]
even_squares = [x**2 for x in range(10) if x % 2 == 0]

# Good - dict comprehension
word_lengths = {word: len(word) for word in words}

# Bad - too complex (use regular loop instead)
result = [x**2 if x % 2 == 0 else x**3 for x in range(100) 
          if x > 10 and x < 90 and x % 3 == 0]

# Better - use regular loop for complex logic
result = []
for x in range(10, 90):
    if x % 3 == 0:
        if x % 2 == 0:
            result.append(x**2)
        else:
            result.append(x**3)

Context Managers (with statement)

# Good - file automatically closes
with open('data.txt', 'r') as f:
    data = f.read()

# Bad - file might not close on error
f = open('data.txt', 'r')
data = f.read()
f.close()

# Good - multiple context managers
with open('input.txt', 'r') as infile, \
     open('output.txt', 'w') as outfile:
    data = infile.read()
    outfile.write(data.upper())

Common Pitfalls

None Comparison

# Good - use 'is' for None
if value is None:
    pass

if value is not None:
    pass

# Bad - don't use ==
if value == None:  # Wrong!
    pass

# Bad - don't use negation
if not value is None:  # Confusing!
    pass

Default Mutable Arguments

# Bad - mutable default argument (creates shared list!)
def append_to_list(item, lst=[]):
    lst.append(item)
    return lst

# Good - use None as default
def append_to_list(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

Exception Handling

# Good - specific exceptions
try:
    result = risky_operation()
except ValueError as e:
    print(f"Invalid value: {e}")
except FileNotFoundError as e:
    print(f"File not found: {e}")

# Bad - bare except (catches everything, including KeyboardInterrupt!)
try:
    result = risky_operation()
except:  # Too broad!
    print("Something went wrong")

# Good - catch all exceptions explicitly
try:
    result = risky_operation()
except Exception as e:  # Explicit but still broad
    print(f"Error: {e}")

Lambda Functions

# Good - simple, one-line operations
squares = map(lambda x: x**2, numbers)
sorted_items = sorted(items, key=lambda x: x.name)

# Bad - complex lambda (use regular function)
result = map(lambda x: x**2 if x % 2 == 0 else x**3 if x > 10 else x, numbers)

# Better - use regular function for complex logic
def transform(x):
    if x % 2 == 0:
        return x**2
    elif x > 10:
        return x**3
    else:
        return x

result = map(transform, numbers)

Additional Resources