Typing Engine: TypeVar, Generic, Protocol, and get_type_hints
- Last updated
- Prerequisites:
- Python Type Annotations (PEP 484)
- OOP Abstract Base Classes
- python
- typing
- typevar
- protocol
- generics
- structural-typing
- pydantic
Read at your depth
The practical view
Python type hints (PEP 484) provide static typing for IDEs and linters (Mypy, Pyright). In modern Python (3.12+), you can define generics using the native syntax `class Stack[T]:` (replacing `TypeVar('T')`). `typing.Protocol` enables **Structural Subtyping** (static duck typing): any class that implements the methods declared in the protocol matches the type without explicit inheritance. `typing.get_type_hints()` allows runtime inspection of annotations.
The same idea in other frameworks
`class Renderable(Protocol): def render(self) -> str: ...`.`interface Renderable { render(): string; }` (structural typing).`type Renderable interface { Render() string }` (implicit interfaces).`trait Renderable { fn render(&self) -> String; }`.Legacy vs modern
Nominal Inheritance (ABC) vs Structural Typing (Protocol)
ABCs require rigid class inheritance; `Protocol` allows any class with matching methods to satisfy the type statically.
class Renderer(ABC): @abstractmethod def render(self): ... // ❌ Forces explicit subclassingclass Renderer(Protocol): def render(self) -> str: ... // ✅ NEW: Static duck typingInterview gotchas
Context
Interviewers test your knowledge of annotation evaluation performance and runtime reflection.
The mechanical answer
Normally, type annotations are evaluated as live Python expressions when the module loads (meaning `x: SomeClass` requires `SomeClass` to already be defined). With `from __future__ import annotations`, **all type annotations are stored as raw strings in `__annotations__` without being evaluated**. This prevents circular import errors and speeds up module load times, but requires runtime libraries (like Pydantic v1) to use `typing.get_type_hints()` to evaluate the strings.
Trap
If using stringified annotations, accessing `obj.__annotations__['x']` directly returns the string `'SomeClass'` instead of the class object; always use `typing.get_type_hints()`.
Context
Checks knowledge of runtime `isinstance()` checks on structural protocols.
The mechanical answer
By default, Protocols are strictly static (for Mypy/Pyright) and calling `isinstance(obj, MyProtocol)` will raise a `TypeError`. Decorating the protocol with **`@runtime_checkable`** allows `isinstance()` and `issubclass()` to inspect whether the object has the required method/attribute names at runtime.
Trap
`@runtime_checkable` only checks method *existence*, not method *type signatures* or return types at runtime.