← Blog · · ConvertCaseHQ Team · 6 min read

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 namingPEP 8 styleExample
ModuleShort, all lowercase; underscores if they helpbilling.py, tax_rules.py
PackageShort, all lowercase; underscores discouragedpayments
ClassCapWordsInvoiceSender
ExceptionCapWords, ending in Error if it's an errorPaymentDeclinedError
Function, methodsnake_casesend_invoice()
Variable, instance variablesnake_casetotal_price
First method argumentself, or cls for class methodsdef from_settings(cls, settings)
ConstantCapitals with underscores, usually at module levelMAX_RETRIES
Type variableShort CapWordsT, AnyStr
Non-public nameOne 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 than clss, 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 works

Constants 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):
    pass

Ruff 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 Error suffix on exceptions.
  • Capitals with underscores for module-level constants, plus Final if 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.

© 2026 ConvertCaseHQ. All rights reserved.