Skip to content

TypeVar and Generic

TypeVar defines type variables for generic functions and classes, allowing flexible yet type-safe code.

TypeVar - Generic Type Variables

Use TypeVar to write generic functions that work with any type.

from typing import TypeVar

T = TypeVar('T')

def first(items: list[T]) -> T:
    return items[0]

def last(items: list[T]) -> T:
    return items[-1]

nums = [1, 2, 3]
strs = ['a', 'b', 'c']

print(first(nums))  # int
print(last(strs))   # str
1
c

Constrained TypeVar

Restrict TypeVar to specific types.

from typing import TypeVar

# Only int or str
T = TypeVar('T', int, str)

def process(value: T) -> T:
    if isinstance(value, int):
        return value * 2  # type: ignore
    else:
        return value.upper()  # type: ignore

print(process(5))
print(process("hello"))
10
HELLO

Generic Classes

Create generic classes with type parameters.

from typing import Generic, TypeVar

T = TypeVar('T')

class Container(Generic[T]):
    def __init__(self, item: T):
        self.item = item

    def get(self) -> T:
        return self.item

int_container = Container[int](42)
str_container = Container[str]("hello")

print(int_container.get())
print(str_container.get())
42
hello

Runnable Example: generics_tutorial.py

"""
Tutorial 07: Generic Types and TypeVar
======================================

Level: Advanced

This tutorial covers generic types, TypeVar, and how to create
flexible, reusable functions and classes that work with multiple types.

Learning Objectives:
- Understand and use TypeVar
- Create generic functions
- Build generic classes
- Use constraints and bounds on TypeVars
- Work with multiple TypeVars

Prerequisites:
- All previous tutorials
"""

from typing import TypeVar, Generic, List, Optional, Sequence, Callable

# =============================================================================
# SECTION 1: TypeVar Basics
# =============================================================================

# TypeVar creates a type variable that can represent any type
T = TypeVar('T')

def identity(value: T) -> T:
    """Return the same value - preserves type."""
    return value

# Usage:
x: int = identity(5)  # T is int
y: str = identity("hello")  # T is str


def get_first(items: Sequence[T]) -> Optional[T]:
    """Get first item, preserving type."""
    return items[0] if items else None


def reverse_list(items: List[T]) -> List[T]:
    """Reverse a list, preserving element type."""
    return list(reversed(items))


# =============================================================================
# SECTION 2: Multiple TypeVars
# =============================================================================

T1 = TypeVar('T1')
T2 = TypeVar('T2')

def pair(first: T1, second: T2) -> tuple[T1, T2]:
    """Create a pair of potentially different types."""
    return (first, second)


def map_pair(pair: tuple[T1, T2], func1: Callable[[T1], T1], func2: Callable[[T2], T2]) -> tuple[T1, T2]:
    """Apply functions to both elements of a pair."""
    return (func1(pair[0]), func2(pair[1]))


# =============================================================================
# SECTION 3: Generic Classes
# =============================================================================

class Stack(Generic[T]):
    """Generic stack data structure."""

    def __init__(self) -> None:
        self._items: List[T] = []

    def push(self, item: T) -> None:
        """Push an item onto the stack."""
        self._items.append(item)

    def pop(self) -> T:
        """Pop an item from the stack."""
        if not self._items:
            raise IndexError("Pop from empty stack")
        return self._items.pop()

    def peek(self) -> Optional[T]:
        """Peek at top item without removing."""
        return self._items[-1] if self._items else None

    def is_empty(self) -> bool:
        """Check if stack is empty."""
        return len(self._items) == 0


class Pair(Generic[T1, T2]):
    """Generic pair of two potentially different types."""

    def __init__(self, first: T1, second: T2) -> None:
        self.first = first
        self.second = second

    def get_first(self) -> T1:
        return self.first

    def get_second(self) -> T2:
        return self.second

    def swap(self) -> 'Pair[T2, T1]':
        """Return a new pair with swapped elements."""
        return Pair(self.second, self.first)


# =============================================================================
# SECTION 4: Constrained TypeVars
# =============================================================================

from typing import Union

# TypeVar with constraints
NumberType = TypeVar('NumberType', int, float)

def add(x: NumberType, y: NumberType) -> NumberType:
    """Add two numbers (int or float)."""
    return x + y  # type: ignore


def multiply(x: NumberType, y: NumberType) -> NumberType:
    """Multiply two numbers."""
    return x * y  # type: ignore


# =============================================================================
# SECTION 5: Bounded TypeVars
# =============================================================================

class Comparable:
    """Base class for comparable objects."""
    def __lt__(self, other: 'Comparable') -> bool:
        raise NotImplementedError

ComparableType = TypeVar('ComparableType', bound=Comparable)

def find_min(items: Sequence[ComparableType]) -> Optional[ComparableType]:
    """Find minimum item (must be comparable)."""
    if not items:
        return None
    return min(items)


# =============================================================================
# SECTION 6: Practical Examples
# =============================================================================

class Box(Generic[T]):
    """A container that holds a value."""

    def __init__(self, value: T) -> None:
        self._value = value

    def get(self) -> T:
        """Get the contained value."""
        return self._value

    def set(self, value: T) -> None:
        """Set a new value."""
        self._value = value

    def map(self, func: Callable[[T], T]) -> 'Box[T]':
        """Apply a function to the contained value."""
        return Box(func(self._value))


def filter_list(items: List[T], predicate: Callable[[T], bool]) -> List[T]:
    """Filter items using a predicate."""
    return [item for item in items if predicate(item)]


if __name__ == "__main__":
    print("=== Generic Types Examples ===\n")

    # Basic TypeVar
    print("TypeVar:")
    print(f"  identity(5): {identity(5)}")
    print(f"  identity('hello'): {identity('hello')}")
    print(f"  get_first([1,2,3]): {get_first([1,2,3])}")
    print()

    # Generic classes
    print("Generic Classes:")
    int_stack: Stack[int] = Stack()
    int_stack.push(1)
    int_stack.push(2)
    print(f"  Stack pop: {int_stack.pop()}")

    str_stack: Stack[str] = Stack()
    str_stack.push("hello")
    print(f"  String stack peek: {str_stack.peek()}")
    print()

    # Pair
    p = Pair(10, "hello")
    print(f"  Pair: ({p.get_first()}, {p.get_second()})")
    swapped = p.swap()
    print(f"  Swapped: ({swapped.get_first()}, {swapped.get_second()})")