Python PEP 8 Naming Conventions: The Complete Guide

12 min read Python

PEP 8 is Python's official style guide, and it has very specific rules about naming. Following these conventions makes your code instantly recognizable as "Pythonic" - the hallmark of professional Python code. Unlike JavaScript's camelCase or C#'s PascalCase everywhere, Python uses snake_case for most identifiers.

Why PEP 8 Matters

PEP 8 (Python Enhancement Proposal 8) was written by Python's creator Guido van Rossum and has become the de facto standard for Python code. Tools like pylint, flake8, and black all enforce PEP 8 rules. When you follow PEP 8:

  • Your code is easier for other Python developers to read
  • You can collaborate more effectively on open-source projects
  • Automated tools can check and format your code
  • Your code feels "Pythonic" rather than translated from another language

Convert to Python Naming

Transform JavaScript camelCase to Python snake_case instantly

snake_case - The Python Standard

Unlike most other languages, Python uses snake_case (all lowercase with underscores) for variables, functions, and methods. This is the most distinctive feature of Python naming.

Variables

All variable names should use snake_case:

# Good
user_name = "Alice"
total_count = 0
is_authenticated = False
shopping_cart_items = []

# Bad - don't do this in Python
userName = "Alice"        # This is JavaScript style
TotalCount = 0           # This looks like a class
IsAuthenticated = False  # Not Pythonic

Functions and Methods

Function and method names also use snake_case:

# Good
def calculate_total(items):
    return sum(item.price for item in items)

def get_user_data(user_id):
    return database.query(user_id)

def is_valid_email(email):
    return '@' in email

# Bad
def calculateTotal(items):  # JavaScript style
    pass

def GetUserData(user_id):   # Looks like a class
    pass

Method Names in Classes

Instance methods follow the same snake_case convention:

class ShoppingCart:
    def __init__(self):
        self.items = []
    
    def add_item(self, item):
        """Add item to cart - note snake_case"""
        self.items.append(item)
    
    def calculate_total(self):
        """Calculate total - also snake_case"""
        return sum(item.price for item in self.items)
    
    def is_empty(self):
        """Check if empty - snake_case for boolean methods too"""
        return len(self.items) == 0

PascalCase - Classes Only

In Python, PascalCase (also called CapWords) is reserved exclusively for class names. Every word starts with a capital letter, with no separators.

# Good - classes use PascalCase
class UserAccount:
    pass

class DatabaseConnection:
    pass

class PaymentProcessor:
    pass

class HTTPClient:  # Acronyms stay uppercase
    pass

# Bad - don't use snake_case for classes
class user_account:  # Looks like a function
    pass

class database_connection:  # Not Pythonic
    pass

Exception Classes

Exception classes should end with "Error" and use PascalCase:

class ValidationError(Exception):
    pass

class DatabaseConnectionError(Exception):
    pass

class InvalidInputError(ValueError):
    pass

SCREAMING_SNAKE_CASE - Constants

Constants in Python use all uppercase letters with underscores. These are module-level values that should never change.

# Good - constants at module level
MAX_CONNECTIONS = 100
API_BASE_URL = "https://api.example.com"
DEFAULT_TIMEOUT = 30
PI = 3.14159

# Use in code
if connection_count > MAX_CONNECTIONS:
    raise ConnectionError("Too many connections")

# Constants in classes (class attributes)
class Config:
    DATABASE_HOST = "localhost"
    DATABASE_PORT = 5432
    MAX_RETRIES = 3

What Counts as a Constant?

Use SCREAMING_SNAKE_CASE for:

  • Module-level constants that never change
  • Configuration values
  • Mathematical constants (PI, E, etc.)
  • Default values that are truly constant

Don't use it for:

  • Variables that might change (even if they rarely do)
  • Function results that you cache
  • Class instances that happen to be singleton

Special Naming Patterns

Private and Internal Names

Python uses underscores to indicate privacy levels:

class User:
    def __init__(self, name):
        self.name = name           # Public attribute
        self._internal = None      # Internal (convention)
        self.__private = None      # Private (name mangling)
    
    def public_method(self):
        """Anyone can call this"""
        pass
    
    def _internal_method(self):
        """Internal use - convention only"""
        pass
    
    def __private_method(self):
        """Private - name gets mangled"""
        pass

Single Leading Underscore (_name)

Indicates "internal use" - a convention that says "don't use this outside this module/class, but it's not enforced."

Double Leading Underscore (__name)

Triggers name mangling - Python actually renames the attribute to _ClassName__name to avoid conflicts in subclasses. Use sparingly.

Double Leading and Trailing (__name__)

Reserved for Python's special methods (dunder methods). Don't create your own:

# These are defined by Python - don't make your own
def __init__(self):      # Constructor
def __str__(self):       # String representation  
def __len__(self):       # Length
def __getitem__(self, key):  # Index access

Boolean Variable Names

Unlike JavaScript, Python doesn't use "is" prefix for booleans (the methods already have "is" in the name for clarity):

# Good - simple adjectives
active = True
valid = False
authenticated = False

# Good - verb forms work too
has_permission = True
can_edit = False

# Avoid - redundant "is" prefix
is_active = True      # Redundant
is_authenticated = True  # Not Pythonic

# Methods can use "is" prefix
def is_valid(self):   # Method name - this is fine
    return self.valid

Module and Package Names

Module (file) names should be short, lowercase, and can use underscores:

# Good module names
utils.py
database.py
user_authentication.py
data_processing.py

# Bad module names
Utils.py              # Don't capitalize
user-authentication.py  # Use underscores, not hyphens
dataProcessing.py     # Not camelCase

Package (directory) names should be short and lowercase, preferably without underscores:

# Good package structure
mypackage/
    __init__.py
    core.py
    utils.py

# Okay if needed
my_package/
    __init__.py

# Bad
MyPackage/           # Don't capitalize
my-package/          # Can't use hyphens in imports

Common PEP 8 Mistakes

Mistake #1: Using camelCase for Functions

def getUserData():

def get_user_data():

Mistake #2: Using snake_case for Classes

class user_account:

class UserAccount:

Mistake #3: Lowercase Constants

max_connections = 100

MAX_CONNECTIONS = 100

Mistake #4: Creating Custom Dunder Methods

def __my_method__(self):

def my_method(self): or def _my_method(self):

PEP 8 Enforcement Tools

pylint

Comprehensive Python linter that checks naming conventions:

# Install
pip install pylint

# Run
pylint my_module.py

# Configure in .pylintrc
[BASIC]
good-names=i,j,k,_
bad-names=foo,bar,baz

flake8

Fast linter focusing on style violations:

# Install
pip install flake8

# Run
flake8 my_module.py

# Configure in .flake8
[flake8]
max-line-length = 88
ignore = E203,W503

black

Opinionated code formatter that enforces PEP 8:

# Install
pip install black

# Format files
black my_module.py

# Check without changing
black --check my_module.py

Real-World Example

Here's a complete example showing all PEP 8 naming conventions:

"""User authentication module following PEP 8."""

# Module-level constants
MAX_LOGIN_ATTEMPTS = 3
SESSION_TIMEOUT = 3600
DEFAULT_ROLE = "guest"


class UserAccount:
    """Represents a user account in the system."""

    def __init__(self, username, email):
        self.username = username
        self.email = email
        self._login_attempts = 0
        self.__password_hash = None

    def authenticate(self, password):
        """Authenticate user with password."""
        if self._login_attempts >= MAX_LOGIN_ATTEMPTS:
            raise AuthenticationError("Too many login attempts")

        if self._verify_password(password):
            self._login_attempts = 0
            return True

        self._login_attempts += 1
        return False

    def _verify_password(self, password):
        """Internal password verification."""
        # Implementation details
        return True

    def __repr__(self):
        """String representation of user."""
        return f"UserAccount(username='{'self.username'}')"


class AuthenticationError(Exception):
    """Raised when authentication fails."""
    pass


def create_user_account(username, email, role=DEFAULT_ROLE):
    """Create a new user account with given details."""
    account = UserAccount(username, email)
    account.role = role
    return account


def is_valid_email(email):
    """Check if email format is valid."""
    return "@" in email and "." in email

Quick Reference

TypeConventionExample
Variablessnake_caseuser_name
Functionssnake_caseget_user_data()
ClassesPascalCaseUserAccount
ConstantsSCREAMING_SNAKE_CASEMAX_CONNECTIONS
Moduleslowercaseuser_auth.py
Private_leading_underscore_internal_method

Remember

  • Use snake_case for nearly everything except classes
  • Classes use PascalCase exclusively
  • Constants use SCREAMING_SNAKE_CASE
  • Single underscore prefix for internal/private
  • Use tools like pylint and black to enforce