Python PEP 8 Naming: snake_case for Variables and Functions

PEP 8 says function and variable names should be lowercase, with words separated by underscores as needed for readability. That's snake_case: total_price, send_invoice(). Classes use CapWords (InvoiceSender), constants use capitals with underscores (MAX_RETRIES), and modules get short, all-lowercase names. That covers most of the Python you'll write.
The rest of PEP 8's naming section is where the useful detail lives: what leading and trailing underscores mean, how to treat acronyms, which single letters to avoid, and when breaking the rules is the right call. Everything below is checked against PEP 8 itself, and anything in quotation marks is PEP 8's own wording.
PEP 8 naming conventions at a glance
| What you're naming | PEP 8 style | Example |
|---|---|---|
| Module | Short, all lowercase; underscores if they help | billing.py, tax_rules.py |
| Package | Short, all lowercase; underscores discouraged | payments |
| Class | CapWords | InvoiceSender |
| Exception | CapWords, ending in Error if it's an error | PaymentDeclinedError |
| Function, method | snake_case | send_invoice() |
| Variable, instance variable | snake_case | total_price |
| First method argument | self, or cls for class methods | def from_settings(cls, settings) |
| Constant | Capitals with underscores, usually at module level | MAX_RETRIES |
| Type variable | Short CapWords | T, AnyStr |
| Non-public name | One leading underscore | _rate_cache |
Here's most of the table in one small module:
# billing.py
MAX_RETRIES = 3
_rate_cache = {}
class PaymentDeclinedError(Exception):
pass
class InvoiceSender:
def __init__(self, smtp_host):
self.smtp_host = smtp_host
self._attempts = 0
def send_invoice(self, invoice_id):
self._attempts += 1
...
@classmethod
def from_settings(cls, settings):
return cls(settings["smtp_host"])Two rarer rows are worth knowing. Type variables can take a _co or _contra suffix to mark covariance or contravariance, as in VT_co = TypeVar('VT_co', covariant=True). And global variables follow the function rules; if a module is meant to be used with from module import *, PEP 8 recommends listing its public names in __all__.
Why snake_case at all?
PEP 8 dates from July 2001, and its listed authors are Guido van Rossum, Barry Warsaw and Alyssa Coghlan. Its starting point is one of Guido's observations: code is read much more often than it is written. The naming rules serve that goal. Underscores keep word boundaries visible even in long names (days_since_last_login), and the split between snake_case values and CapWords classes tells you what kind of thing a name is before you look it up. PEP 8 also sets an overriding principle for public APIs: names should reflect how something is used, not how it's implemented.
What the underscores mean
Python has no private keyword, so underscores do that job by convention. PEP 8 recognizes four forms:
_single_leading: a weak "internal use" signal.from module import *skips these names, but nothing else stops anyone using them.single_trailing_: avoids a clash with a keyword.class_is better thanclss, though PEP 8 notes that a synonym can be better still.__double_leading: inside a class, this triggers name mangling. PEP 8 suggests it only to avoid clashes with attributes in subclasses, and admits some people dislike it.__double_leading_and_trailing__: "magic" names such as__init__and__file__. PEP 8 says never invent these; only use the documented ones.
Name mangling surprises people who treat it as privacy. It's a rename, not a lock:
class Account:
def __init__(self):
self.__pin = "1234"
acct = Account()
acct.__pin # AttributeError
acct._Account__pin # "1234": the mangled name still worksConstants are a convention, not a rule
Writing a name in capitals doesn't stop anyone reassigning it; Python will happily run MAX_RETRIES = 4. If you want a tool to enforce it, annotate the name with typing.Final:
from typing import Final
MAX_RETRIES: Final = 3
MAX_RETRIES = 4 # still runs, but mypy reports:
# Cannot assign to final name "MAX_RETRIES"Enum members are the other place capitals show up. The Python docs' enum HOWTO strongly recommends UPPER_CASE member names, partly because enums represent constants and partly to avoid clashes with method names: class Color(Enum) with members RED and GREEN.
Acronyms, single letters and other edge cases
Acronyms in class names
PEP 8 says to capitalize every letter of an acronym in CapWords, so HTTPServerError beats HttpServerError. The standard library follows it in places such as http.server.HTTPServer and json.JSONDecodeError. In snake_case, acronyms simply go lowercase: parse_json, http_status.
That matters if you rename with a tool. Our case converter turns processOrder into process_order and HTTPServerError into http_server_error, which is exactly right for snake_case. But its PascalCase output writes acronyms as words, so HTTP server error comes out as HttpServerError, and you'd fix the capitals by hand to match PEP 8.
Letters to avoid
Never use l (lowercase L), O (uppercase o) or I (uppercase i) as single-character names. In some fonts they're indistinguishable from 1 and 0.
mixedCase
camelCase function names are allowed only where that's already the prevailing style, for backwards compatibility. PEP 8's own example is the threading module.
Why the standard library breaks its own rules
PEP 8 admits that the naming in Python's library is "a bit of a mess". You'll meet logging.getLogger() and unittest's assertEqual() in mixedCase, and lowercase classes such as datetime.datetime. Built-in types like list and dict are lowercase on purpose: PEP 8 describes a separate convention for built-ins, with CapWords used only for exception names and built-in constants.
Two rules from PEP 8 explain why these names mostly stay as they are. First, where an existing library has a different style, internal consistency is preferred. Second, the section titled "A Foolish Consistency is the Hobgoblin of Little Minds": consistency with PEP 8 matters, consistency within a project matters more, and consistency within a module or function matters most. Don't break backwards compatibility just to comply. That's why a unittest test class still defines setUp(): the framework calls that exact name.
Check names automatically with Ruff or pep8-naming
You don't need to review names by eye. The flake8 plugin pep8-naming checks them, and Ruff implements the same checks as its N rules. Given this file:
maxRetries = 3
class order_processor:
def processOrder(self, orderId):
totalPrice = 0
return totalPrice
class InvalidOrder(Exception):
passRuff flags every name in it:
$ ruff check --select N --output-format concise orders.py
orders.py:1:1: N816 Variable `maxRetries` in global scope should not be mixedCase
orders.py:4:7: N801 Class name `order_processor` should use CapWords convention
orders.py:5:9: N802 Function name `processOrder` should be lowercase
orders.py:5:28: N803 Argument name `orderId` should be lowercase
orders.py:6:9: N806 Variable `totalPrice` in function should be lowercase
orders.py:10:7: N818 Exception name `InvalidOrder` should be named with an Error suffix
Found 6 errors.To turn the rules on for a whole project, add them to pyproject.toml:
[tool.ruff.lint]
extend-select = ["N"]When you're renaming one identifier through a pasted file, our find and replace tool shows how many matches it changed, which is a quick sanity check before you paste the result back. For a whole codebase, use your editor's rename refactoring instead, so references in other files follow.
A PEP 8 naming checklist
- snake_case for functions, methods, variables and arguments.
- CapWords for classes, with acronyms in capitals and an
Errorsuffix on exceptions. - Capitals with underscores for module-level constants, plus
Finalif you want a checker to enforce them. - Short lowercase module names; avoid underscores in package names.
- One leading underscore for internal names; double underscores only to avoid subclass clashes.
- When an existing library uses another style, match the library.
Moving between languages? Our developer cheat sheet lines up Python's rules against JavaScript, Java, Go, Rust and others.


