Python Naming Conventions

Naming things is one of the hardest parts of programming, but Python makes it easier by having clear, well-established conventions. Following these conventions makes your code instantly recognizable to other Python developers.

Why Naming Conventions Matter

Consistent naming tells readers what kind of thing they're looking at — a variable, a class, a constant — without needing to check the definition. PEP 8, Python's official style guide, codifies these conventions so the entire ecosystem speaks the same language.

snake_case for Variables, Functions, Methods, and Modules

The most common naming style in Python is snake_case — all lowercase with words separated by underscores:

# Variables
user_name = "Alice"
total_count = 42
is_active = True

# Functions
def calculate_total(price, tax_rate):
    return price * (1 + tax_rate)

# Methods
class ShoppingCart:
    def add_item(self, item):
        self.items.append(item)

    def get_total_price(self):
        return sum(item.price for item in self.items)

# Modules (file names)
# my_module.py
# database_utils.py
# test_helpers.py

Python Playground
Output
Click "Run" to execute your code

UpperCamelCase (PascalCase) for Classes

Class names use UpperCamelCase, where each word starts with a capital letter and there are no underscores:

class HttpClient:
    pass

class DatabaseConnection:
    pass

class UserAuthenticationManager:
    pass

This convention applies to all classes, including those in inheritance hierarchies and polymorphic base classes. When you see a CamelCase name, you immediately know it's a class — even if it's an abstract base class or a mixin:

from abc import ABC, abstractmethod

class Shape(ABC):
    """Base class for all shapes (polymorphic base)."""

    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

Python Playground
Output
Click "Run" to execute your code

UPPER_SNAKE_CASE for Constants

Constants — values that shouldn't change — use all uppercase with underscores:

MAX_CONNECTIONS = 100
DEFAULT_TIMEOUT = 30
PI = 3.14159265358979
DATABASE_URL = "postgresql://localhost/mydb"
HTTP_STATUS_OK = 200

Note: Python doesn't enforce immutability for constants. UPPER_SNAKE_CASE is a signal to other developers: "don't reassign this." It's a social contract, not a language feature.

_single_leading_underscore for Internal Use

A single leading underscore signals "this is for internal use" — it's a convention, not enforcement:

class BankAccount:
    def __init__(self, balance):
        self._balance = balance  # Internal — use the property instead

    @property
    def balance(self):
        return self._balance

    def _validate_amount(self, amount):
        """Internal helper — not part of the public API."""
        if amount <= 0:
            raise ValueError("Amount must be positive")

When you do from module import *, names starting with _ are not imported. This is the one case where the underscore has a real effect beyond convention.

__double_leading_underscore for Name Mangling

Double leading underscores trigger Python's name mangling mechanism. The interpreter rewrites the attribute name to include the class name, which prevents accidental overrides in subclasses:

class Base:
    def __init__(self):
        self.__secret = "base secret"  # Becomes _Base__secret

class Child(Base):
    def __init__(self):
        super().__init__()
        self.__secret = "child secret"  # Becomes _Child__secret

Name mangling is specifically designed for inheritance hierarchies. When a subclass defines __secret, it doesn't collide with the parent's __secret because Python renames them differently based on the class they're defined in. This interacts with the Method Resolution Order (MRO) — the mangled name is determined by the class where the attribute is defined, not where it's accessed:

class Base:
    def __init__(self):
        self.__value = 10  # Stored as _Base__value

    def get_value(self):
        return self.__value  # Accesses _Base__value

class Child(Base):
    def __init__(self):
        super().__init__()
        self.__value = 20  # Stored as _Child__value

    def get_child_value(self):
        return self.__value  # Accesses _Child__value

Python Playground
Output
Click "Run" to execute your code

When to use it: Rarely. Most of the time, a single underscore (_name) is enough. Use double underscores only when you specifically need to avoid name collisions in complex inheritance hierarchies.

__dunder__ Methods (Magic Methods)

Names with double leading and trailing underscores are "dunder" (double underscore) methods — also called magic methods or special methods. These are reserved by Python and define how objects behave with built-in operations:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __str__(self):
        return f"({self.x}, {self.y})"

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __len__(self):
        return 2

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

These dunder methods are the backbone of polymorphism in Python. When you call len(obj), Python calls obj.__len__(). When you use ==, it calls __eq__(). This means any class can participate in Python's built-in protocols simply by implementing the right dunder methods:

Dunder Method Triggered By Purpose
__init__ MyClass() Initialize a new instance
__str__ str(obj), print(obj) Human-readable string
__repr__ repr(obj), REPL display Developer-readable string
__eq__ a == b Equality comparison
__len__ len(obj) Length of container
__iter__ for x in obj Make object iterable
__getitem__ obj[key] Bracket access
__add__ a + b Addition operator
__contains__ x in obj Membership test

Python Playground
Output
Click "Run" to execute your code

Important: Never invent your own dunder names like __mymethod__. These are reserved for Python itself. If you need a private method, use _mymethod instead.

Why Python Chose snake_case

Python's choice of snake_case has roots in its core philosophy: readability counts (from the Zen of Python). Guido van Rossum formalized this in PEP 8 back in 2001, but the convention predates the PEP.

The reasoning:

  • calculate_total_price is more readable than calculateTotalPrice at a glance
  • Underscores create visual separation between words
  • Consistency with the standard library (which uses snake_case almost everywhere)
  • Python values explicit readability over saving a few keystrokes

This contrasts with languages like Java and JavaScript, which prefer camelCase. Neither is objectively better — it's about consistency within the ecosystem.

Naming Convention Quick Reference

What Convention Example
Variable snake_case user_count
Function snake_case get_user()
Method snake_case obj.calculate_total()
Module (file) snake_case my_module.py
Package (folder) snake_case my_package/
Class UpperCamelCase HttpClient
Exception UpperCamelCase + Error ValueError
Constant UPPER_SNAKE_CASE MAX_RETRIES
Internal _leading_underscore _helper()
Name-mangled __double_leading __private_attr
Magic method __dunder__ __init__()
Type variable UpperCamelCase (short) T, KT, VT

Naming in Class Hierarchies

When building class hierarchies, naming conventions help signal the intent of each class. This becomes especially important with multiple inheritance and mixins.

Base Classes

Base classes are typically named as plain nouns describing the concept:

class Animal:
    """Concrete or abstract base for all animals."""
    pass

class Vehicle:
    """Base class for all vehicle types."""
    pass

Abstract Classes

Prefix or suffix to signal that the class shouldn't be instantiated directly. Python developers commonly use Abstract as a prefix or Base as a suffix:

from abc import ABC, abstractmethod

class AbstractProcessor(ABC):
    """Must be subclassed — cannot be used directly."""

    @abstractmethod
    def process(self, data):
        pass

class BaseHandler:
    """Provides shared logic; subclass to customize."""

    def handle(self, request):
        self.pre_handle(request)
        result = self.do_handle(request)
        self.post_handle(request)
        return result

Mixins

Mixins are small classes designed to be combined with other classes via multiple inheritance. Name them with a Mixin suffix so their purpose is immediately clear:

class LoggingMixin:
    """Add logging capability to any class."""

    def log(self, message):
        print(f"[{self.__class__.__name__}] {message}")

class SerializableMixin:
    """Add JSON serialization to any class."""

    def to_dict(self):
        return self.__dict__.copy()

class ApiClient(LoggingMixin, SerializableMixin):
    def __init__(self, base_url):
        self.base_url = base_url

    def fetch(self, endpoint):
        self.log(f"Fetching {endpoint}")
        return {"url": f"{self.base_url}/{endpoint}"}

When using multiple inheritance, Python follows the Method Resolution Order (MRO) — a linearization of the class hierarchy using the C3 algorithm. The MRO determines which method gets called when multiple parent classes define the same method. You can inspect it with ClassName.__mro__ or ClassName.mro():

Python Playground
Output
Click "Run" to execute your code

Naming Conventions Signal Intent

Good naming in a class hierarchy tells the reader:

  • Animal — this might be a concrete class or an abstract base
  • AbstractAnimal — definitely abstract, must subclass
  • AnimalBase — provides shared functionality, probably meant to be subclassed
  • FlyingMixin — a mixin, designed for multiple inheritance
  • IAnimal — interface style (less common in Python, more Java/C#)

Tip: In Python, prefer AbstractX or XBase for abstract classes and XMixin for mixins. Avoid the IX interface prefix — it's not Pythonic.

Common Mistakes

A few naming pitfalls to avoid:

  • Single-letter names (except in loops or math): x = get_user() tells you nothing
  • Abbreviations: calc_ttl_prc is harder to read than calculate_total_price
  • Misleading names: user_list that's actually a dict, is_valid that returns a string
  • Shadowing built-ins: don't name variables list, dict, type, id, input, or str
# Bad — shadows the built-in
list = [1, 2, 3]         # Now you can't use list() anywhere
type = "admin"            # Now you can't use type() anywhere

# Good — be more specific
user_list = [1, 2, 3]
user_type = "admin"