Introduction
Have you ever opened a Python file and immediately felt confused by inconsistent naming? One function uses camelCase, another uses snake_case. Variables have cryptic single-letter names. Classes are named like functions. This inconsistency makes code harder to read, understand, and maintain.
PEP 8 (Python Enhancement Proposal 8) is the official style guide for Python code. It establishes conventions for naming, formatting, and organizing code that the entire Python community follows. By adhering to these conventions, you write code that’s not just correctโit’s Pythonic.
In this guide, we’ll explore PEP 8 naming conventions, understand why they matter, and learn how to apply them consistently in your projects.
What Is PEP 8?
The Importance of Style Guides
PEP 8 is Python’s official style guide, created by Guido van Rossum (Python’s creator) and other core developers. It provides guidelines for:
- Naming conventions - How to name variables, functions, classes, etc.
- Code formatting - Indentation, line length, whitespace
- Comments and docstrings - How to document code
- Programming recommendations - Best practices and patterns
The key principle behind PEP 8 is stated in the Zen of Python:
“Readability counts.”
Code is read far more often than it’s written. By following consistent conventions, you make your code accessible to other developers (and your future self).
Why Follow PEP 8?
โ
Consistency - Everyone’s code looks similar
โ
Readability - Easier to understand and maintain
โ
Collaboration - Teams work more efficiently
โ
Professional - Industry standard for Python projects
โ
Tooling - Linters and formatters expect PEP 8
Naming Conventions Overview
Python uses different naming styles for different constructs:
| Construct | Convention | Example |
|---|---|---|
| Variables | snake_case | user_name, total_price |
| Functions | snake_case | calculate_total(), get_user() |
| Classes | PascalCase | UserManager, DataProcessor |
| Constants | UPPER_CASE | MAX_ATTEMPTS, API_KEY |
| Modules | snake_case | user_manager.py, data_processor.py |
| Packages | snake_case | user_management, data_processing |
Variables: snake_case
The Rule
Variable names should use snake_case: lowercase letters with underscores separating words.
# Good - clear and readable
user_name = "Alice"
total_price = 99.99
is_active = True
max_attempts = 3
# Avoid - camelCase (used in other languages)
userName = "Alice"
totalPrice = 99.99
isActive = True
# Avoid - unclear abbreviations
un = "Alice"
tp = 99.99
ia = True
Why snake_case?
- Readability - Underscores are easier to read than camelCase
- Consistency - Python standard across the ecosystem
- Accessibility - Better for screen readers and dyslexic developers
Real-World Example
# E-commerce system
class ShoppingCart:
def __init__(self):
self.items = []
self.total_price = 0.0
self.discount_percentage = 0
self.is_checkout_complete = False
def add_item(self, item_name: str, item_price: float) -> None:
self.items.append({"name": item_name, "price": item_price})
self.total_price += item_price
def apply_discount(self, discount_percentage: float) -> None:
self.discount_percentage = discount_percentage
self.total_price *= (1 - discount_percentage / 100)
Functions: snake_case
The Rule
Function names should use snake_case, with verbs that describe what the function does.
# Good - descriptive verbs
def calculate_total(items):
pass
def get_user_by_id(user_id):
pass
def is_valid_email(email):
pass
def send_notification(user_id, message):
pass
# Avoid - unclear names
def calc(items):
pass
def get(user_id):
pass
def check(email):
pass
def notify(user_id, msg):
pass
Naming Patterns
Getters - Use get_ prefix:
def get_user(user_id):
return users[user_id]
def get_total_price(items):
return sum(item["price"] for item in items)
Setters - Use set_ prefix:
def set_user_email(user_id, email):
users[user_id]["email"] = email
def set_discount(percentage):
global discount
discount = percentage
Checkers - Use is_ or has_ prefix:
def is_valid_email(email):
return "@" in email
def has_permission(user, action):
return action in user["permissions"]
def is_empty(collection):
return len(collection) == 0
Converters - Use to_ prefix:
def to_uppercase(text):
return text.upper()
def to_json(data):
return json.dumps(data)
def to_list(generator):
return list(generator)
Classes: PascalCase
The Rule
Class names should use PascalCase (also called CapWords): capitalize the first letter of each word, no underscores.
# Good - PascalCase
class UserManager:
pass
class DataProcessor:
pass
class HTTPClient:
pass
class XMLParser:
pass
# Avoid - snake_case
class user_manager:
pass
class data_processor:
pass
# Avoid - camelCase
class userManager:
pass
class dataProcessor:
pass
Why PascalCase for Classes?
- Distinction - Immediately identifies classes vs. functions
- Convention - Standard across Python and many languages
- Clarity - Makes code more scannable
Real-World Example
class UserAuthentication:
"""Handles user login and authentication."""
def __init__(self, database):
self.database = database
self.session_timeout = 3600
def authenticate_user(self, username: str, password: str) -> bool:
user = self.database.get_user(username)
return user and self._verify_password(password, user["password_hash"])
def _verify_password(self, password: str, hash_value: str) -> bool:
# Implementation details
pass
class EmailNotification:
"""Sends email notifications to users."""
def __init__(self, smtp_server: str):
self.smtp_server = smtp_server
def send_welcome_email(self, user_email: str, user_name: str) -> None:
subject = f"Welcome, {user_name}!"
body = f"Hello {user_name}, welcome to our service!"
self._send_email(user_email, subject, body)
def _send_email(self, recipient: str, subject: str, body: str) -> None:
# Implementation details
pass
Constants: UPPER_CASE
The Rule
Constants (values that don’t change) should use UPPER_CASE with underscores separating words.
# Good - clearly indicates constants
MAX_ATTEMPTS = 3
API_KEY = "your-api-key"
DATABASE_URL = "postgresql://localhost/mydb"
TIMEOUT_SECONDS = 30
DEFAULT_LANGUAGE = "en"
# Avoid - looks like a variable
max_attempts = 3
api_key = "your-api-key"
database_url = "postgresql://localhost/mydb"
Important Note
Python doesn’t enforce constantsโthey’re just a convention. You can still modify them, but the UPPER_CASE name signals “don’t change this.”
# Configuration constants
class Config:
MAX_UPLOAD_SIZE = 10 * 1024 * 1024 # 10 MB
ALLOWED_EXTENSIONS = {".jpg", ".png", ".gif"}
CACHE_EXPIRY_MINUTES = 60
RETRY_ATTEMPTS = 3
# Database configuration
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "production_db"
Private and Protected Names
Single Underscore: Protected
A single leading underscore indicates a name is for internal use:
class DataProcessor:
def __init__(self):
self._internal_state = {}
self._cache = []
def process(self, data):
"""Public method."""
return self._process_internal(data)
def _process_internal(self, data):
"""Protected method - for internal use."""
# Implementation
pass
Double Underscore: Private
Double leading underscores trigger name mangling, making the name harder to access:
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private
def deposit(self, amount):
"""Public method."""
self.__update_balance(amount)
def __update_balance(self, amount):
"""Private method - name mangled to _BankAccount__update_balance."""
self.__balance += amount
def get_balance(self):
return self.__balance
# Usage
account = BankAccount(1000)
account.deposit(500)
print(account.get_balance()) # Output: 1500
# Accessing private members (possible but discouraged)
# print(account._BankAccount__balance) # Works but bad practice
When to Use Each
- No underscore - Public API, intended for external use
- Single underscore - Internal implementation, use with caution
- Double underscore - Rarely needed; use only to prevent name conflicts in subclasses
Special Methods: Dunder Names
What Are Dunder Methods?
“Dunder” methods (double underscore) are special methods that Python calls automatically. They’re always written with double underscores on both sides:
class Product:
def __init__(self, name: str, price: float):
"""Constructor - called when creating an instance."""
self.name = name
self.price = price
def __str__(self) -> str:
"""String representation for users."""
return f"Product: {self.name} (${self.price})"
def __repr__(self) -> str:
"""String representation for developers."""
return f"Product(name='{self.name}', price={self.price})"
def __eq__(self, other) -> bool:
"""Equality comparison."""
return self.name == other.name and self.price == other.price
def __lt__(self, other) -> bool:
"""Less than comparison."""
return self.price < other.price
def __len__(self) -> int:
"""Length of the product name."""
return len(self.name)
# Usage
product1 = Product("Laptop", 999.99)
product2 = Product("Mouse", 29.99)
print(str(product1)) # Output: Product: Laptop ($999.99)
print(repr(product1)) # Output: Product(name='Laptop', price=999.99)
print(product1 == product2) # Output: False
print(product2 < product1) # Output: True
print(len(product1)) # Output: 6
Modules and Packages
Module Names
Module names (Python files) should use snake_case:
# Good
user_manager.py
data_processor.py
email_service.py
api_client.py
# Avoid
UserManager.py
DataProcessor.py
EmailService.py
APIClient.py
Package Names
Package names (directories) should use snake_case:
# Good directory structure
my_project/
โโโ user_management/
โ โโโ __init__.py
โ โโโ models.py
โ โโโ services.py
โ โโโ utils.py
โโโ data_processing/
โ โโโ __init__.py
โ โโโ processors.py
โ โโโ validators.py
โโโ api_client/
โโโ __init__.py
โโโ client.py
# Avoid
my_project/
โโโ UserManagement/
โโโ DataProcessing/
โโโ APIClient/
Common Naming Pitfalls
Pitfall 1: Single-Letter Variables
# Avoid - unclear what x, y, z represent
def calculate(x, y, z):
return x + y * z
# Better - descriptive names
def calculate(base_amount, tax_rate, discount):
return base_amount + base_amount * tax_rate * discount
Pitfall 2: Ambiguous Abbreviations
# Avoid - unclear abbreviations
def proc_usr_data(ud):
return ud
# Better - clear names
def process_user_data(user_data):
return user_data
Pitfall 3: Misleading Names
# Avoid - name doesn't match what it does
def get_user_count():
"""This actually returns user data, not a count!"""
return fetch_all_users()
# Better - accurate names
def get_all_users():
return fetch_all_users()
def get_user_count():
return len(fetch_all_users())
Pitfall 4: Inconsistent Naming
# Avoid - inconsistent patterns
class UserManager:
def get_user(self, user_id):
pass
def create_user(self, name):
pass
def user_exists(self, user_id): # Inconsistent pattern
pass
def remove_user(self, user_id):
pass
# Better - consistent patterns
class UserManager:
def get_user(self, user_id):
pass
def create_user(self, name):
pass
def has_user(self, user_id): # Consistent with is_/has_ pattern
pass
def delete_user(self, user_id):
pass
Tools for Checking PEP 8 Compliance
flake8
A popular linter that checks PEP 8 compliance:
# Install
pip install flake8
# Check a file
flake8 myfile.py
# Check a directory
flake8 src/
pylint
A comprehensive code analyzer:
# Install
pip install pylint
# Check a file
pylint myfile.py
black
An opinionated code formatter that enforces PEP 8:
# Install
pip install black
# Format a file
black myfile.py
# Format a directory
black src/
Example: Using flake8
$ flake8 example.py
example.py:1:1: E302 expected 2 blank lines, found 0
example.py:5:1: E501 line too long (88 > 79 characters)
example.py:10:1: F841 local variable 'unused_var' is assigned to but never used
Conclusion
Following Python naming conventions and PEP 8 guidelines is essential for writing professional, maintainable code. Here are the key takeaways:
Remember:
- Variables and functions use
snake_case - Classes use
PascalCase - Constants use
UPPER_CASE - Private members use
_single_underscoreor__double_underscore - Modules and packages use
snake_case - Be descriptive - Names should clearly indicate purpose
- Be consistent - Follow the same patterns throughout your codebase
- Use tools - Let flake8, pylint, or black help enforce standards
By adhering to these conventions, you write code that’s not just correctโit’s Pythonic. Your teammates will appreciate the consistency, and your future self will thank you for the clarity.
Start applying these conventions in your next project, and you’ll quickly develop the habit of writing clean, readable Python code.
Happy coding! ๐
Quick Reference
# Variables and functions
user_name = "Alice"
total_price = 99.99
def calculate_total(items):
pass
# Classes
class UserManager:
pass
# Constants
MAX_ATTEMPTS = 3
API_KEY = "secret"
# Private/Protected
class MyClass:
def __init__(self):
self._protected = "internal"
self.__private = "very internal"
def _protected_method(self):
pass
def __private_method(self):
pass
# Modules
# user_manager.py
# data_processor.py
# Packages
# user_management/
# data_processing/
Comments