Skip to the content.
Managed Polymorphism: Using 'Any' and Class Names | AI Systems Design From Scratch

Connect with Amin Boulouma Official

AI Systems Design From First Principles - An implementation of AI Systems Design From First Principles | Product Hunt

🏠 Documentation Hub 📝 Engineering Blog 💻 GitHub Repository

Managed Polymorphism: Type Flexibility

Amin Boulouma, Software Engineer

Polymorphism allows your code to treat different objects as instances of a general interface. In dynamic languages like Python, we often manage this using typing.Any or by inspecting the object’s class name at runtime.

The Challenge: Type Safety vs. Flexibility

When a function accepts “anything,” you lose the benefits of static analysis. “Managed” polymorphism is about retaining control—ensuring that even if you accept Any type, your logic safely identifies and handles the specific child class before proceeding.

Pattern 1: Runtime Inspection

Instead of relying solely on duck-typing, you can use the object’s class name or isinstance checks to branch your logic. This is common when your SocketServer needs to handle different command types differently.

from typing import Any

def handle_request(obj: Any) -> None:
    # Use __class__.__name__ to manage polymorphic behavior
    class_name = obj.__class__.__name__
    
    if class_name == "LoginRequest":
        _handle_login(obj)
    elif class_name == "DataRequest":
        _handle_data(obj)
    else:
        raise ValueError(f"Unsupported polymorphic type: {class_name}")

Pattern 2: The Any Type Hint

Using Any is a signal that you are bypassing strict type checking. To “manage” this, use it in conjunction with Type Guards or Factory Patterns.

Best Practices for Managed Polymorphism

  1. Prefer isinstance() over __class__.__name__: Checking the name of the class is brittle (it breaks if you rename the class). isinstance(obj, BaseClass) is more robust and idiomatic Python.
  2. Exhaustive Handling: If you are using polymorphism to switch between types, ensure you have an else clause that logs or raises an error for unexpected types.
  3. Use Protocol (Structural Typing): If you find yourself checking __class__.__name__ frequently, you are likely missing a shared interface. Define a Protocol to describe what methods these polymorphic objects should have.
from typing import Protocol

class Request(Protocol):
    def process(self) -> None:
        ...

# Now your server handles any object that satisfies the 'Request' protocol
def execute(req: Request) -> None:
    req.process()

Summary Checklist

Connect with Amin Boulouma Official