Skip to content

Python – Naming Conventions

Python naming conventions come directly from PEP 8, the official style guide authored by Guido van Rossum. Following them ensures that Cygnus Dynamics Python code is immediately readable by any Python developer — internal or external — and works cleanly with tools like mypy, pylint, and IDEs.

Enforcement

These conventions are enforced automatically via pylint and flake8 in all Python projects. Your IDE (PyCharm or VS Code with Pylance) will highlight violations inline. Pre-commit hooks prevent non-conforming code from being committed.

Quick Reference

Identifier Convention Example
Module / Package snake_case (short, lowercase) order_service, user_utils
Class PascalCase OrderService, PaymentProcessor
Exception PascalCase + Error suffix OrderNotFoundError, ValidationError
Function snake_case (verb) calculate_total(), fetch_user()
Method snake_case (verb) get_order_by_id(), process_payment()
Variable snake_case order_total, user_email_address
Constant UPPER_SNAKE_CASE MAX_RETRY_COUNT, DEFAULT_TIMEOUT_SEC
Type variable PascalCase short T, KT, AnyStr
Private (module) _single_leading_underscore _internal_cache
Private (class) __double_leading_underscore self.__order_id
Dunder / magic __double_both_sides__ __init__, __str__

Packages and Modules

Package and module names must be short, all-lowercase. Use underscores to improve readability in module names — but avoid them in package (directory) names.

# ✅ Correct
import order_service
import user_utils
from cygnus.payments import gateway

# ❌ Incorrect
import OrderService       # PascalCase not allowed
import order-service      # hyphens not valid Python identifiers
import orderServiceUtils  # camelCase not used in Python

Package structure at Cygnus Dynamics:

cygnus/
├── orders/           # package — no underscores
│   ├── __init__.py
│   ├── service.py        # module — snake_case
│   ├── repository.py
│   └── models.py
├── payments/
│   ├── gateway.py
│   └── processor.py
└── common/
    ├── exceptions.py
    └── validators.py

Classes

Class names use PascalCase and should be nouns that clearly describe what the class represents.

# ✅ PascalCase nouns
class OrderService:
    pass

class PaymentProcessor:
    pass

class UserProfileRepository:
    pass

class HTTPRequestHandler:    # Acronyms fully capitalised in PascalCase
    pass

# ❌ Incorrect
class order_service:         # snake_case — not allowed
class processOrders:         # camelCase — not Python convention
class Mgr:                   # abbreviation — avoid
class DoTheThing:            # verb phrase — use a noun

Common class naming patterns:

Class Type Naming Pattern Example
Service <Domain>Service OrderService
Repository <Domain>Repository UserRepository
Model / Entity <Domain> (plain noun) Order, User, Payment
Exception <Description>Error OrderNotFoundError
Mixin <Capability>Mixin TimestampMixin, AuditMixin
Abstract base Base<Domain> or Abstract<Domain> BaseRepository
Data class <Domain> or <Domain>Data OrderSummary, UserData
Configuration <Domain>Config or <Domain>Settings DatabaseConfig
Enum <Domain> (plain noun) OrderStatus, UserRole

Exceptions

All custom exceptions must:

  • Inherit from a built-in exception (Exception, ValueError, RuntimeError, etc.)
  • End with the Error suffix
  • Use PascalCase
# ✅ Correct — descriptive name, Error suffix, appropriate base class
class OrderNotFoundError(Exception):
    def __init__(self, order_id: int) -> None:
        super().__init__(f"Order not found: order_id={order_id}")
        self.order_id = order_id


class InsufficientStockError(ValueError):
    def __init__(self, product_id: int, requested: int, available: int) -> None:
        super().__init__(
            f"Insufficient stock for product {product_id}: "
            f"requested={requested}, available={available}"
        )
        self.product_id = product_id


class PaymentDeclinedError(RuntimeError):
    pass


# ❌ Incorrect
class OrderNotFound(Exception):     # missing Error suffix
    pass

class order_not_found_error(Exception):  # snake_case — wrong
    pass

Functions and Methods

Function and method names must be snake_case and start with a verb that describes the action.

# ✅ Descriptive verb-first snake_case
def calculate_order_total(items: list) -> float:
    pass

def fetch_user_by_email(email: str) -> "User":
    pass

def validate_payment_details(card: dict) -> bool:
    pass

def send_confirmation_email(user: "User", order: "Order") -> None:
    pass

# ❌ Incorrect
def OrderTotal(items):    # PascalCase is for classes
def getData():            # camelCase — not Python convention
def d(x):                 # single letter — meaningless
def order(id):            # noun, not a verb

Boolean-returning Functions

Functions that return a boolean should be named as a clear yes/no question:

# ✅ Reads naturally in a conditional
def is_order_eligible_for_discount(order: "Order") -> bool:
    pass

def has_active_subscription(user: "User") -> bool:
    pass

def can_process_refund(order: "Order") -> bool:
    pass

# Usage:
if is_order_eligible_for_discount(order):
    apply_discount(order)

Method Argument Conventions

Always use self for instance methods and cls for class methods — no exceptions.

class OrderService:

    def get_order(self, order_id: int) -> "Order":    # instance method — self
        pass

    @classmethod
    def from_config(cls, config: dict) -> "OrderService":  # class method — cls
        pass

    @staticmethod
    def validate_order_id(order_id: int) -> bool:          # static — no self/cls
        pass

If a parameter name clashes with a Python built-in or reserved keyword, append a single trailing underscore:

# ✅ Trailing underscore to avoid collision with 'type', 'id', 'class'
def create_event(type_: str, id_: int) -> None:
    pass

# ❌ Corruption / abbreviation — worse than trailing underscore
def create_event(typ: str, eid: int) -> None:
    pass

Variables

Variables use snake_case. Names must be meaningful — a reader should understand the value's purpose without reading the surrounding logic.

# ✅ Descriptive snake_case
order_total = calculate_total(cart_items)
active_user_count = len([u for u in users if u.is_active])
default_currency_code = "USD"

# ❌ Vague, wrong case, or abbreviated
t = calculate_total(cart)       # single letter — meaningless
OrderTotal = calculate_total()  # PascalCase is for classes
actUsrCnt = len(users)          # camelCase abbreviation — not Pythonic

Names to Avoid Completely

Never use these as single-character variable names — in many fonts they are visually indistinguishable from digits:

Name Looks Like
l (lowercase L) 1 (the number one)
O (uppercase O) 0 (the number zero)
I (uppercase I) 1 (the number one)
# ❌ Banned — visually ambiguous
l = 1
O = 0
I = 1

# ✅ Use descriptive names or safe alternatives
length = 1
count = 0
index = 1

Constants

Module-level constants are written in UPPER_SNAKE_CASE. They should be placed near the top of the module, after imports.

# ✅ Module-level constants — UPPER_SNAKE_CASE
MAX_RETRY_COUNT = 3
DEFAULT_PAGE_SIZE = 20
SESSION_TIMEOUT_SEC = 1800
API_BASE_URL = "https://api.cygnusdynamics.com/v2"
SUPPORTED_CURRENCIES = frozenset({"USD", "EUR", "GBP", "AED"})

# ✅ Use numeric underscores for readability
ONE_DAY_SECONDS = 86_400
MAX_UPLOAD_BYTES = 10_485_760   # 10 MB

# ❌ Wrong case — looks like a variable
max_retry_count = 3
MaxRetryCount = 3

Underscore Conventions

Python uses underscores to communicate visibility and intent. Understanding these is essential:

Pattern Meaning Example
_name Internal / non-public — convention only, still accessible _cache, _helpers
name_ Avoids clash with Python keyword or built-in type_, id_, class_
__name Class-private — triggers name mangling self.__order_id
__name__ Dunder / magic — defined by Python protocol __init__, __str__
class Order:

    def __init__(self, order_id: int) -> None:
        self.__order_id = order_id      # __private — mangled to _Order__order_id
        self._created_at = None         # _internal — convention, not enforced

    def __repr__(self) -> str:          # __dunder__ — Python protocol
        return f"Order(id={self.__order_id})"

    def _build_summary(self) -> dict:   # _internal method — not part of public API
        pass

Never invent your own __dunder__ names

Dunder names (__like_this__) are reserved by the Python language. Only use them as documented by the data model. Creating your own __custom__ attributes risks clashing with future Python versions.

Type Variables

Type variables introduced with TypeVar use short PascalCase names. Add _co or _contra suffixes for covariant and contravariant type variables.

from typing import TypeVar

T = TypeVar("T")                        # generic type
KT = TypeVar("KT")                      # key type
VT = TypeVar("VT")                      # value type
AnyStr = TypeVar("AnyStr", str, bytes)  # constrained
VT_co = TypeVar("VT_co", covariant=True)
KT_contra = TypeVar("KT_contra", contravariant=True)