Python OOP is more powerful than most developers realize — and most developers have never needed to use __slots__, __getattr__, or abstract base classes. But when you need private state, custom operators, property validation, or enforcing interface contracts, these features are exactly what you reach for. This guide focuses on Python dataclasses, with the production trade-offs senior engineers need to make sound decisions.
⚡ TL;DR: __init__ for initialization. @property for computed attributes with validation. @classmethod for alternative constructors. @staticmethod for utility methods. super() for safe inheritance. ABC for interface contracts. Dunder methods for operator overloading.
Classes, properties, and descriptors — Python dataclasses
class BankAccount:
def __init__(self, owner: str, balance: float = 0):
self._owner = owner
self._balance = float(balance)
@property
def balance(self) -> float: # Getter
return self._balance
@balance.setter
def balance(self, value: float): # Setter with validation
if value < 0:
raise ValueError(f'Balance cannot be negative: {value}')
self._balance = float(value)
@classmethod
def from_dict(cls, data: dict) -> 'BankAccount': # Alt constructor
return cls(data['owner'], data.get('balance', 0))
@staticmethod
def validate_currency(code: str) -> bool: # Utility, no self/cls
return code.upper() in {'USD','EUR','GBP'}Inheritance and super()
class SavingsAccount(BankAccount):
def __init__(self, owner, balance=0, interest_rate=0.05):
super().__init__(owner, balance) # ALWAYS call super().__init__
self._rate = interest_rate
def apply_interest(self):
self.balance += self.balance * self._rate # Uses property validation
@property
def balance(self):
return super().balance # Can override properties
# Multiple inheritance: MRO (Method Resolution Order)
class A: def method(self): return 'A'
class B(A): def method(self): return 'B'+super().method()
class C(A): def method(self): return 'C'+super().method()
class D(B,C): pass
D().method() # 'BCА' — follows C3 linearization MRODunder methods — operator overloading
class Vector:
def __init__(self, x, y): self.x,self.y = x,y
def __repr__(self): return f'Vector({self.x},{self.y})'
def __str__(self): return f'({self.x},{self.y})'
def __len__(self): return 2
def __add__(self, other): return Vector(self.x+other.x,self.y+other.y)
def __mul__(self, scalar): return Vector(self.x*scalar,self.y*scalar)
def __eq__(self, other): return self.x==other.x and self.y==other.y
def __hash__(self): return hash((self.x,self.y)) # Needed for sets/dicts
def __iter__(self): yield self.x; yield self.y
def __getitem__(self,i): return (self.x,self.y)[i]
v1,v2 = Vector(1,2),Vector(3,4)
v1+v2 # Vector(4,6)
v1*3 # Vector(3,6)
list(v1) # [1,2]Abstract base classes
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ... # Must implement
@abstractmethod
def perimeter(self) -> float: ...
def describe(self) -> str: # Concrete — shared by all
return f'{type(self).__name__}: area={self.area():.2f}'
class Circle(Shape):
def __init__(self, r): self.r = r
def area(self): import math; return math.pi*self.r**2
def perimeter(self): import math; return 2*math.pi*self.r
# Shape() # TypeError: Can't instantiate abstract class
Circle(5).describe() # 'Circle: area=78.54'- ✅ @property for attribute validation without breaking interface
- ✅ super().__init__() always in subclass init
- ✅ __repr__ for debugging, __str__ for display
- ✅ __hash__ required when you define __eq__
- ✅ ABC for enforcing interface contracts
- ❌ Never use name mangling (__attr) unless truly private
- ❌ Never inherit from multiple classes with conflicting __init__
External reference: Python data model documentation.
Recommended Reading
→ Designing Data-Intensive Applications — The bible of distributed systems and production engineering at scale.
→ The Pragmatic Programmer — Timeless engineering wisdom every senior developer needs.
Affiliate links. We earn a small commission at no extra cost to you.
Free Weekly Newsletter
🚀 Join 2,000+ Senior Developers
Get expert-level JavaScript, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.
Related: Python Dictionary Methods and Tricks: The Complete Guide · Python Dictionary Methods and Tricks: The Complete Guide for 2025 · Python String Methods: Every str Method With Real Production Examples
Related: Python Dataclasses and Pydantic: Modern Data Validation for Production APIs · Python Dataclasses: 10 Advanced Features That Make __init__ Obsolete · Python Decorators Explained: From Simple Wrappers to Production Patterns
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Agentic Git Workflows: Atomic Commits From Noisy LLM Diffs
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
Newly added
- Amazon Bedrock Model Evaluation: Score Coding-Agent Outputs Before You Promote a Prompt
- AWS App Runner: Host Coding-Agent HTTP APIs Without Babysitting Containers
- Amazon S3 Object Lock: Immutable Artifact Buckets Coding Agents Cannot Overwrite
- AWS Security Hub: Aggregate Coding-Agent Security Findings Across Accounts
- Amazon Detective: Investigate Compromised Coding-Agent Credentials After GuardDuty
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
