TechLead
Lección 13 de 25
5 min de lectura
Python

Anotaciones de tipo en Python

Aprende a usar anotaciones de tipo para mejor calidad de codigo, soporte de IDE y analisis estatico con mypy

Introduccion a las anotaciones de tipo

Python tiene tipado dinamico, pero desde Python 3.5, puedes agregar anotaciones de tipo (tambien llamadas type hints) a tu codigo. Las anotaciones de tipo no afectan el comportamiento en tiempo de ejecucion — son usadas por herramientas como mypy, IDEs y linters para detectar errores antes de que se ejecute tu codigo. Tambien sirven como excelente documentacion.

# Basic type hints
name: str = "Alice"
age: int = 30
height: float = 5.7
is_active: bool = True

# Function type hints
def greet(name: str) -> str:
    return f"Hello, {name}!"

def add(a: int, b: int) -> int:
    return a + b

# None return type
def log_message(message: str) -> None:
    print(f"[LOG] {message}")

# Optional parameters
def find_user(user_id: int, active_only: bool = True) -> str:
    return f"User {user_id}"

Tipos de coleccion

# Python 3.9+ - use built-in types directly
numbers: list[int] = [1, 2, 3]
coordinates: tuple[float, float] = (3.14, 2.72)
unique_ids: set[str] = {"abc", "def"}
scores: dict[str, int] = {"Alice": 95, "Bob": 87}

# Variable-length tuples
values: tuple[int, ...] = (1, 2, 3, 4, 5)

# Nested types
matrix: list[list[int]] = [[1, 2], [3, 4]]
users: dict[str, list[str]] = {
    "admins": ["Alice", "Bob"],
    "users": ["Charlie", "Diana"],
}

# Python 3.8 and earlier - use typing module
from typing import List, Dict, Tuple, Set
numbers: List[int] = [1, 2, 3]  # Legacy syntax

Union, Optional y tipos avanzados

from typing import Optional, Union

# Union types - can be one of several types
# Python 3.10+ syntax
def process(value: int | str) -> str:
    return str(value)

# Pre-3.10 syntax
def process_old(value: Union[int, str]) -> str:
    return str(value)

# Optional - shorthand for Union[X, None]
def find_user(user_id: int) -> Optional[dict]:
    """Returns user dict or None if not found."""
    if user_id == 1:
        return {"id": 1, "name": "Alice"}
    return None

# Python 3.10+ equivalent
def find_user_new(user_id: int) -> dict | None:
    pass

# Literal types - restrict to specific values
from typing import Literal

def set_color(color: Literal["red", "green", "blue"]) -> None:
    print(f"Color set to {color}")

set_color("red")     # OK
# set_color("purple")  # mypy error!

# TypeAlias for complex types
type UserId = int
type UserMap = dict[UserId, str]

# Pre-3.12 syntax:
from typing import TypeAlias
UserId: TypeAlias = int

Tipos Callable y Protocol

from typing import Callable, Protocol

# Callable - type hint for functions
def apply_operation(
    values: list[int],
    operation: Callable[[int], int]
) -> list[int]:
    return [operation(v) for v in values]

result = apply_operation([1, 2, 3], lambda x: x ** 2)
print(result)  # [1, 4, 9]

# Callable with keyword args
Handler = Callable[[str, int], bool]

# Protocol - structural subtyping (duck typing with types)
class Renderable(Protocol):
    def render(self) -> str: ...

class HTMLElement:
    def render(self) -> str:
        return "
Hello
" class MarkdownText: def render(self) -> str: return "# Hello" def display(item: Renderable) -> None: """Accepts any object with a render() -> str method.""" print(item.render()) display(HTMLElement()) # Works display(MarkdownText()) # Works - no inheritance needed!

Generics

from typing import TypeVar, Generic

# TypeVar for generic functions
T = TypeVar("T")

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

x: int = first([1, 2, 3])       # T is inferred as int
y: str = first(["a", "b", "c"]) # T is inferred as str

# Generic classes
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

    def peek(self) -> T:
        return self._items[-1]

    def is_empty(self) -> bool:
        return len(self._items) == 0

int_stack: Stack[int] = Stack()
int_stack.push(42)
# int_stack.push("hello")  # mypy error!

# Python 3.12+ syntax (simpler!)
def first_new[T](items: list[T]) -> T:
    return items[0]

class Stack_new[T]:
    def __init__(self) -> None:
        self._items: list[T] = []

Ejecutar mypy

# Install mypy
# pip install mypy

# Run type checking
# mypy myfile.py
# mypy src/

# Configuration in pyproject.toml
# [tool.mypy]
# python_version = "3.12"
# strict = true
# warn_return_any = true
# warn_unused_configs = true
# disallow_untyped_defs = true

Puntos clave

  • Las anotaciones de tipo son opcionales: No afectan el tiempo de ejecucion pero mejoran la calidad del codigo
  • Usa sintaxis moderna: list[int] sobre List[int], int | None sobre Optional[int]
  • Protocol para duck typing: Define interfaces sin herencia
  • Ejecuta mypy en CI: Detecta errores de tipo antes de que lleguen a produccion

Continuar Aprendiendo