Advanced OOP, ABCs & Structural Protocols
Design extensible object architectures using Abstract Base Classes (ABCs), static duck-typing via `typing.Protocol`, custom attribute descriptors, and pythonic dunder protocols.
Interactive Sandbox Studio: 4 Tracks & 26 Competencies
Switch tracks and select any of the 26 core topics below to run production Python code, inspect CPython memory mechanics, and execute unit test assertion suites in real time.
Nominal vs Structural Subtyping in Python
- ABCs (Nominal Inheritance): Inherit from `abc.ABC` and enforce mandatory interface implementation at instantiation time using `@abstractmethod`.
- Protocols (Structural Duck-Typing): Inherit from `typing.Protocol` to define implicit compile-time contracts verified by static type checkers like Mypy without strict inheritance dependencies.
1. `@property`, `@classmethod`, and `@staticmethod`
`@property` encapsulates attribute access with getter/setter logic. `@classmethod` receives the class reference (`cls`) to implement alternative constructors. `@staticmethod` operates as an isolated utility bound to the class namespace.
class BankAccount:
def __init__(self, owner: str, balance: float):
self.owner = owner
self._balance = max(0.0, balance)
@property
def balance(self) -> float:
"""Read-only property getter."""
return self._balance
@classmethod
def create_empty(cls, owner: str):
"""Alternative constructor initializing empty account."""
return cls(owner=owner, balance=0.0)
@staticmethod
def validate_amount(amount: float) -> bool:
"""Utility method with no access to self or cls."""
return amount > 0.0
acc = BankAccount.create_empty("Alice")
print(acc.owner, acc.balance)
2. Abstract Base Classes (ABC) vs `typing.Protocol`
ABCs prevent direct instantiation of incomplete subclasses. Protocols enforce structural subtyping (compile-time interface contracts).
from abc import ABC, abstractmethod
from typing import Protocol
# 1. Nominal Interface via ABC
class StorageBackend(ABC):
@abstractmethod
def save(self, data: dict) -> bool:
pass
# 2. Structural Contract via Protocol
class Renderable(Protocol):
def render(self) -> str: ...
class DashboardWidget:
def render(self) -> str:
return "<div class='widget'>Widget Content</div>"
def display_component(item: Renderable):
print("Render Output:", item.render())
display_component(DashboardWidget())
3. Attribute Descriptors (`__get__`, `__set__`, `__delete__`)
Descriptors customize attribute lookup, assignment, and deletion behavior across objects. They power Python ORMs (SQLAlchemy, Django ORM), validation fields, and `@property`.
class NonEmptyString:
"""Descriptor enforcing non-empty string validation."""
def __set_name__(self, owner, name):
self.private_name = "_" + name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.private_name, "")
def __set__(self, instance, value):
if not isinstance(value, str) or not value.strip():
raise ValueError("Value must be a non-empty string!")
setattr(instance, self.private_name, value)
class Product:
name = NonEmptyString()
p = Product()
p.name = "TeachCloud Pro"
print("Product Name:", p.name)