Object-Oriented Python Best Practices
Python is not Java - lean toward simple objects, functions, and protocols rather than deep inheritance hierarchies. These rules keep OO code Pythonic and testable.
Search across all documentation pages
Python is not Java - lean toward simple objects, functions, and protocols rather than deep inheritance hierarchies. These rules keep OO code Pythonic and testable.
@dataclass for data records; add methods only when behavior is intrinsic. Not every dict needs a class.has-a) over inheritance (is-a) beyond one level. Inject collaborators in __init__.Enum/StrEnum instead of string constants. Exhaustive match and typo prevention.super().__init__ in cooperative multiple inheritance chains. Broken chains skip initialization.Protocol or ABC only when multiple implementations exist. YAGNI for single impl.@classmethod factories for alternate constructors. Not overloaded __init__ patterns._. Properties expose stable attributes over raw fields.__eq__ and __hash__ for value objects used in sets/dicts. Or use frozen=True dataclass.__post_init__ or setters. Fail fast at construction.NotImplemented from __eq__ for unknown types. Let other operand try reverse comparison.__init__.__init_subclass__ for registration hooks. Not custom metaclasses.__init__ otherwise.__slots__ only with measured memory pressure. Accept inheritance constraints.+ hurts readers.Framework extension points, small mixins, true subtype polymorphism with few levels.
dataclass in domain core; Pydantic where JSON/schema validation required.
No - modules of functions are Pythonic. Classes when state + behavior bundle naturally.
Protocol for structural third-party types; ABC when you control inheritance and want instantiation guards.
No fixed max - if class scrolls endlessly, split by responsibility or extract helpers.
__name mangling rare in app code - single _ convention sufficient.
class MyDict(dict) sparingly - often wrap or compose instead for clearer APIs.
Keep anemic ORM rows separate from rich domain when complexity grows - anti-corruption layer.
Construct with minimal fields; use replace() for frozen variants in tests.
Deep inheritance replacing simple functions - refactor to composition and protocols.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 16, 2026