Python Identifiers and Naming Conventions — PEP 8 Guide

Sanjeev SharmaSanjeev Sharma
4 min read

Advertisement

Introduction

Why This Matters

In Python, every name you give to a variable, function, class, or module is an identifier. Python's rules for what constitutes a valid identifier are strict — break them and your code won't run. But beyond correctness, how you name things is equally important for readability and professionalism.

The Python community follows PEP 8, the official style guide, which establishes naming conventions that are used across the entire ecosystem. When you contribute to open-source projects, pass code review, or work in a team, following PEP 8 naming is not optional — it is expected. Poor naming is one of the most common code review comments and one of the easiest things to fix.

Understanding naming also unlocks Python's special underscore conventions — _private, __dunder__, __mangled — which carry semantic meaning in classes and modules.

Rules for Valid Identifiers

An identifier must:

  • Start with a letter (a-z, A-Z) or underscore _
  • Contain only letters, digits (0-9), or underscores
  • Not be a Python keyword
# Valid identifiers
user_name = "Alice"
_count = 0
MAX_SIZE = 100
UserProfile = object()
x1 = 10
 
# Invalid identifiers
# 1user = "Alice"   — starts with digit
# user-name = "Bob" — hyphen not allowed
# class = "A"       — reserved keyword

Python Keywords

These are reserved and cannot be used as identifiers:

False   None    True    and     as      assert  async   await
break   class   continue def     del     elif    else    except
finally for     from    global  if      import  in      is
lambda  nonlocal not     or      pass    raise   return  try
while   with    yield
import keyword
print(keyword.kwlist)  # full list of reserved keywords

PEP 8 Naming Conventions

EntityConventionExample
Variablesnake_caseuser_name, total_count
Functionsnake_casecalculate_tax(), get_user()
ClassPascalCaseUserProfile, HttpClient
ConstantUPPER_SNAKE_CASEMAX_SIZE, API_URL
Modulesnake_caseuser_utils.py
Packagelowercasemypackage/
# Variables and functions: snake_case
user_count = 42
first_name = "Alice"
 
def calculate_area(radius: float) -> float:
    return 3.14159 * radius ** 2
 
# Classes: PascalCase
class DatabaseConnection:
    pass
 
class HttpRequestHandler:
    pass
 
# Constants: UPPER_SNAKE_CASE (convention only — not enforced)
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30
API_BASE_URL = "https://api.example.com"

Underscore Conventions

Python uses underscores to convey special meaning:

# Single leading underscore: internal/private by convention
class MyClass:
    def __init__(self):
        self._internal_state = "private"  # hint: don't access externally
 
    def _helper(self):
        pass  # internal method
 
# Double leading underscore: name mangling (harder to access from subclasses)
class Base:
    def __init__(self):
        self.__secret = "mangled"  # stored as _Base__secret
 
# Double leading and trailing: dunder (magic) methods
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"
 
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
 
# Single trailing underscore: avoid conflict with keyword
class_ = "Python101"   # avoid shadowing 'class' keyword
type_ = "integer"

Module-Level Identifiers

# __all__ controls what's exported with 'from module import *'
__all__ = ["PublicClass", "public_function"]
 
# Module metadata
__version__ = "1.2.3"
__author__ = "Alice"
 
def public_function():
    pass
 
def _private_function():
    pass

Type Alias Naming (Python 3.12+)

from typing import TypeAlias
 
UserId: TypeAlias = int
UserMap: TypeAlias = dict[int, str]
 
# Python 3.12+ type statement
type Matrix = list[list[float]]

Common Mistakes

  • Using camelCase for functions/variables instead of snake_case
  • Naming a variable list, dict, type, or id — shadows built-ins
  • Using single-letter names (except i, j, k in loops or x, y for coordinates)
  • Inconsistent naming style across a module
  • Using double underscores (__name) without understanding name mangling

Best Practices

  • Follow PEP 8 naming conventions consistently throughout your project
  • Use descriptive names — user_count is better than uc or n
  • Avoid abbreviations unless they are universally understood (url, db, api)
  • Run pycodestyle or ruff to enforce naming conventions automatically
  • Use __all__ in modules to explicitly declare the public API

Key Takeaways

  • Identifiers must start with a letter or underscore, contain only letters/digits/underscores
  • Python has 35 reserved keywords that cannot be used as identifiers
  • Variables and functions use snake_case; classes use PascalCase; constants use UPPER_SNAKE_CASE
  • A single leading underscore _name signals private by convention; Python does not enforce it
  • Double leading underscores __name trigger name mangling for subclass protection
  • Dunder methods (__init__, __repr__) are Python magic methods — never create your own
  • Using built-in names as identifiers (list = []) is valid but hides the built-in type

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading