Lab 01: Advanced OOP

Objective

Master Python's advanced OOP features: abstract base classes, dataclasses, descriptors, metaclasses, __slots__, multiple inheritance MRO, and the Protocol pattern.

Time

35 minutes

Prerequisites

  • Python Foundations Labs 01–05

Tools

  • Docker image: zchencow/innozverse-python:latest


Lab Instructions

Step 1: Dataclasses & Field Validation

docker run --rm zchencow/innozverse-python:latest python3 -c "
from dataclasses import dataclass, field, fields, asdict
from typing import ClassVar

@dataclass
class Product:
    name: str
    price: float
    stock: int = 0
    tags: list[str] = field(default_factory=list)
    _registry: ClassVar[list] = []

    def __post_init__(self):
        if self.price <= 0:
            raise ValueError(f'price must be positive, got {self.price}')
        if self.stock < 0:
            raise ValueError(f'stock cannot be negative')
        self.name = self.name.strip()
        Product._registry.append(self)

    @property
    def status(self) -> str:
        return 'out_of_stock' if self.stock == 0 else 'active'

    def __str__(self) -> str:
        return f'[{self.name}] \${self.price:.2f} stock={self.stock} ({self.status})'

@dataclass(frozen=True)   # immutable
class SKU:
    vendor: str
    code: str

    def __str__(self):
        return f'{self.vendor}-{self.code}'

p1 = Product('Surface Pro 12\"', 864.00, 15, ['laptop', 'microsoft'])
p2 = Product('Surface Pen', 49.99, 80)

print(p1)
print('Dict:', asdict(p1))
print('Fields:', [f.name for f in fields(p1)])

sku = SKU('MSFT', 'SRF-PRO-12')
print('SKU:', sku)

try:
    Product('Bad', -10)
except ValueError as e:
    print('Error:', e)
"

💡 @dataclass auto-generates __init__, __repr__, __eq__ from type-annotated fields. field(default_factory=list) creates a new list per instance (never share a mutable default). frozen=True makes the dataclass immutable and hashable — perfect for dict keys and set members.

📸 Verified Output:


Step 2: Abstract Base Classes

📸 Verified Output:


Step 3: Descriptors

💡 Descriptors (__get__, __set__, __set_name__) are the mechanism behind Python's property, classmethod, staticmethod, and ORM field definitions. When you write Column(String) in SQLAlchemy, Column is a descriptor. This lab shows how to build your own reusable validation layer.

📸 Verified Output:


Steps 4–8: slots, MRO, Metaclass, Protocol, Capstone

📸 Verified Output:


Summary

Feature
Use case

@dataclass

Auto-generate boilerplate, validated models

ABC + @abstractmethod

Enforce interface contracts

Descriptors

Reusable field validation, ORM columns

__slots__

Memory-efficient classes with fixed attributes

MRO (C3)

Predictable multiple inheritance resolution

Metaclass

Class factories, singletons, registries

Protocol

Duck typing with type-checker support

Further Reading

Last updated