Python Dataclasses: Classes, Inheritance, Dunder Methods, and Dataclasses Explained

Python OOP: Classes, Inheritance, Dunder Methods, and Dataclasses Explained
3 views

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 MRO

Dunder 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.

✓ No spam✓ Unsubscribe anytime✓ Expert-level only

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

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.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.