Python & AI Tutorials Logo
Programación Python

42. Introducción sencilla a las sugerencias de tipo (opcional)

A lo largo de este libro, has escrito código Python sin especificar qué tipos de datos contienen tus variables o qué tipos aceptan y devuelven tus funciones. Python ha funcionado perfectamente bien así: es un lenguaje de tipado dinámico (dynamically typed), lo que significa que los tipos se determinan en tiempo de ejecución a medida que tu programa se ejecuta. Esta flexibilidad es una de las mayores fortalezas de Python, ya que te permite escribir código de forma rápida y expresiva.

Sin embargo, a medida que los programas crecen y se vuelven más grandes y complejos, esta flexibilidad a veces puede hacer que el código sea más difícil de entender y mantener. Cuando ves una función como def process_data(items):, podrías preguntarte: ¿Qué tipo de datos contiene items? ¿Una lista de cadenas? ¿Un diccionario? ¿Algo completamente distinto?

Las sugerencias de tipo (type hints) (también llamadas anotaciones de tipo (type annotations)) proporcionan una forma de documentar los tipos esperados en tu código. Son adiciones opcionales a Python que pueden hacer tu código más claro, ayudar a detectar errores antes y habilitar potentes funciones del IDE, todo ello sin cambiar cómo Python ejecuta realmente tu código.

Este capítulo introduce las sugerencias de tipo de manera sencilla, mostrándote qué son, por qué existen y cómo usarlas de forma efectiva. Debido a que las sugerencias de tipo son opcionales y no afectan a cómo Python ejecuta tu código, todo este capítulo está marcado como opcional. Puedes escribir excelentes programas de Python sin usar nunca sugerencias de tipo. Pero entenderlas te ayudará a leer código Python moderno y a decidir cuándo podrían beneficiar a tus propios proyectos.

42.1) Por qué se añadieron las sugerencias de tipo a Python

Python se diseñó desde el principio como un lenguaje de tipado dinámico. Durante décadas, los programadores de Python escribieron código sin ninguna información de tipos, y esto funcionó maravillosamente para innumerables proyectos. Entonces, ¿por qué se añadieron las sugerencias de tipo a Python en 2015 (con Python 3.5)?

El desafío de las bases de código grandes

A medida que Python se volvió más popular para aplicaciones a gran escala, los equipos se encontraron con desafíos:

python
# En una base de código grande, ¿qué espera y devuelve esta función?
def calculate_discount(customer, items, code):
    # ... 50 líneas de código ...
    return result

Sin leer todo el cuerpo de la función o su documentación, no puedes saber:

  • ¿customer es un diccionario, un objeto personalizado o algo distinto?
  • ¿items es una lista, una tupla o un conjunto?
  • ¿Qué tipo es code: una cadena, un entero?
  • ¿Qué devuelve la función: un número, un diccionario o quizá None?

En programas pequeños, esta ambigüedad es manejable. Puedes mirar fácilmente cómo se usa la función en otros lugares. Pero en una base de código con miles de funciones repartidas en docenas de archivos, esto se vuelve difícil.

La solución: sugerencias de tipo opcionales

Los creadores de Python decidieron añadir un sistema opcional para documentar tipos. La palabra clave es "opcional": las sugerencias de tipo son completamente voluntarias. Puedes usarlas cuando ayuden, ignorarlas cuando no, y mezclar código anotado y no anotado libremente.

Aquí tienes un ejemplo simple para mostrar la sintaxis básica:

python
# Sin sugerencias de tipo
def add(a, b):
    return a + b
 
# Con sugerencias de tipo
def add(a: int, b: int) -> int:
    return a + b

La sintaxis es directa:

  • Dos puntos (:) después de un parámetro muestra qué tipo debería ser: a: int
  • Flecha (->) antes de los dos puntos muestra qué tipo devuelve la función: -> int

Ahora veamos esto con nuestro ejemplo anterior:

python
def calculate_discount(customer: dict, items: list, code: str) -> float:
    # ... 50 lines of code ...
    return result

Ahora queda claro de inmediato: customer es un diccionario, items es una lista, code es una cadena, y la función devuelve un float.

No te preocupes si esta sintaxis te parece poco familiar: la exploraremos en detalle en las secciones 42.3-42.6. Por ahora, solo fíjate en cómo puedes ver de un vistazo qué espera la función y qué devuelve.

Con o sin sugerencias de tipo, la función funciona exactamente igual: Python no comprueba estos tipos en tiempo de ejecución. (Exploraremos este punto importante en detalle en la sección 42.2)

Un enfoque gradual y pragmático

El sistema de sugerencias de tipo de Python fue diseñado para ser:

  1. Opcional: nunca tienes que usar sugerencias de tipo
  2. Gradual: puedes añadir sugerencias a algunas partes de tu código y a otras no
  3. No intrusivo: las sugerencias no cambian cómo Python ejecuta tu código
  4. Compatible con herramientas: herramientas externas pueden comprobar las sugerencias, pero Python en sí las ignora en tiempo de ejecución

Este enfoque pragmático permite que Python siga siendo flexible a la vez que proporciona beneficios para quienes los quieren.

No, gracias

Sí, por favor

Código Python

¿Añadir sugerencias de tipo?

✓ Funciona perfectamente
✓ Manténlo simple

Añadir sugerencias de tipo

✓ Mismo comportamiento en tiempo de ejecución

✓ Mejor soporte del IDE

✓ Detectar errores antes

42.2) La regla de oro: sin aplicación en tiempo de ejecución

Lo más importante que debes entender sobre las sugerencias de tipo es esto: Python no aplica las sugerencias de tipo en tiempo de ejecución. Son puramente informativas. Veamos qué significa esta realidad sorprendente en la práctica.

Las sugerencias de tipo no evitan tipos incorrectos

Considera esta función con sugerencias de tipo:

python
def greet(name: str) -> str:
    return f"Hello, {name}!"
 
# Esto funciona bien, aunque 42 no es una cadena
result = greet(42)
print(result)  # Output: Hello, 42!

La sugerencia de tipo dice claramente que name debería ser una cadena, pero Python acepta felizmente el entero 42 y ejecuta la función. Python no comprueba la sugerencia de tipo; simplemente usa el valor que le proporcionas.

Esto es fundamentalmente diferente de lenguajes como Java o C++, donde el compilador comprueba los tipos antes de ejecutar tu código y se niega a ejecutarlo si hay una incompatibilidad de tipos. El enfoque de Python es más permisivo: confía en que proporciones los tipos correctos, pero no te obliga.

El problema: los riesgos del tipado dinámico siguen ahí

Aquí está el verdadero desafío: incluso con sugerencias de tipo, el tipado dinámico de Python significa que aún puedes cometer errores de tipo que solo aparecen en tiempo de ejecución:

python
def calculate_total(prices: list) -> float:
    """Calcular la suma de precios."""
    return sum(prices)
 
# Esto funciona bien
print(calculate_total([10.99, 5.50, 3.25]))  # Output: 19.74
 
# ¡Pero esto falla en tiempo de ejecución!
print(calculate_total("not a list"))  # TypeError: 'str' object is not iterable

La sugerencia de tipo dice claramente que prices debería ser una lista, pero Python no te impide pasar una cadena. El error solo aparece cuando el código realmente se ejecuta e intenta usar sum() con la cadena.

¡Esto es frustrante! Añadimos sugerencias de tipo para detectar estos problemas, pero los riesgos del tipado dinámico siguen ahí. Los errores de tipo pueden ocultarse en tu código hasta el tiempo de ejecución, pudiendo aparecer en producción cuando un usuario hace algo inesperado.

Entonces, si las sugerencias de tipo no evitan errores en tiempo de ejecución, ¿para qué sirve usarlas?

Entonces, ¿para qué sirven las sugerencias de tipo?

Puede que las sugerencias de tipo no cambien el comportamiento de Python en tiempo de ejecución, pero cumplen un propósito crucial: proporcionan información a personas y herramientas, no a Python en sí:

  1. Documentación: te dicen qué tipos espera y devuelve una función
  2. Soporte del IDE: tu editor puede usar sugerencias para ofrecer autocompletado y mostrar advertencias
  3. Análisis estático: herramientas externas (como mypy) pueden comprobar tu código en busca de errores de tipo antes de que lo ejecutes
  4. Comprensión del código: hacen que las bases de código grandes sean más fáciles de leer y mantener

Piensa en las sugerencias de tipo como comentarios que las herramientas pueden entender. No cambian cómo se ejecuta Python, pero te ayudan a escribir mejor código.

Pero, ¿cómo nos ayuda esto realmente a detectar esos errores en tiempo de ejecución que acabamos de ver?

La solución: sugerencias de tipo + soporte del IDE

Aquí es donde las sugerencias de tipo realmente brillan. Aunque Python no las aplica en tiempo de ejecución, tu IDE puede detectar errores antes incluso de que ejecutes el código:

python
def add_numbers(a: int, b: int) -> int:
    """Sumar dos números."""
    return a + b
 
# Tu IDE mostrará una advertencia aquí (antes de ejecutar el código)
result = add_numbers("Hello", "World")  # IDE: Warning - expected int, got str

Tu editor de código ve las sugerencias de tipo y puede advertirte sobre incompatibilidades de tipos mientras escribes, mucho antes de que ejecutes el código. Esto detecta muchos bugs durante el desarrollo en lugar de en producción.

El desarrollo moderno en Python normalmente funciona así:

  1. Escribes código con sugerencias de tipo
  2. Tu IDE muestra advertencias cuando los tipos no coinciden
  3. Corriges los problemas antes de ejecutar el código
  4. Los errores en tiempo de ejecución por incompatibilidades de tipos se vuelven mucho más raros

La sugerencia de tipo no evita el error en tiempo de ejecución, ¡pero tu IDE la usa para evitar que escribas código con bugs desde el principio!

Lo mejor de ambos mundos

Las sugerencias de tipo le dan a Python lo mejor de ambos mundos: detectar la mayoría de los errores temprano mientras mantiene la flexibilidad:

Seguridad en desarrollo: tu IDE y los verificadores de tipos detectan la mayoría de los errores de tipo durante el desarrollo, así que encuentras bugs pronto.

python
def process(data: list) -> list:
    return [x * 2 for x in data]
 
# Si accidentalmente pasas una cadena:
process("hello")  # IDE warns: expected list, got str
# ¡Lo arreglas antes de ejecutar el código!

Flexibilidad en tiempo de ejecución: Python aún ejecuta código con incompatibilidades de tipos, lo cual puede ser útil para prototipado rápido o cuando intencionalmente quieres aceptar múltiples tipos.

python
def add_numbers(a: int, b: int) -> int:
    return a + b
 
# Python ejecutará esto, aunque los tipos no coincidan
print(add_numbers(5.5, 3.2))        # Output: 8.7 (works!)
print(add_numbers("Hi", " there"))  # Output: Hi there (also works!)

Esta flexibilidad significa que no estás encerrado en un sistema de tipos rígido. Cuando necesitas romper las reglas (para pruebas, prototipado o casos de uso legítimos), Python te lo permite. Pero cuando estás escribiendo código de producción, tu IDE te mantiene a salvo.

Recuerda la regla de oro: las sugerencias de tipo no cambian el comportamiento de Python en tiempo de ejecución; solo te dan a ti y a tus herramientas la información necesaria para detectar problemas pronto. Aún necesitas tener cuidado, pero ahora tienes aliados poderosos cuidándote.

42.3) Anotar funciones: parámetros y valores de retorno

El uso más común de las sugerencias de tipo es anotar parámetros de funciones y valores de retorno. Esto les dice a los lectores (y a las herramientas) qué tipos espera y produce una función. Empecemos con el caso más simple y vayamos construyendo gradualmente.

Lo básico: anotaciones de parámetros

Para añadir una sugerencia de tipo a un parámetro, coloca dos puntos después del nombre del parámetro, seguido del tipo:

python
def greet(name: str):
    """Saludar a una persona por su nombre."""
    return f"Hello, {name}!"
 
# Usage
message = greet("Alice")
print(message)  # Output: Hello, Alice!

La sintaxis name: str significa "el parámetro name debería ser una cadena". Puedes añadir sugerencias de tipo a múltiples parámetros:

python
def calculate_area(width: float, height: float):
    """Calcular el área de un rectángulo."""
    return width * height
 
# Usage
area = calculate_area(5.0, 3.0)
print(area)  # Output: 15.0

Aquí, tanto width como height están anotados como float. La función funciona igual que antes: las sugerencias de tipo no cambian el comportamiento, pero ahora tu IDE sabe qué tipos esperar.

Añadir anotaciones de tipo de retorno

Para especificar qué tipo devuelve una función, añade -> type después de la lista de parámetros y antes de los dos puntos:

python
def get_full_name(first: str, last: str) -> str:
    """Combinar nombre y apellido."""
    return f"{first} {last}"
 
# Usage
name = get_full_name("John", "Doe")
print(name)  # Output: John Doe

El -> str significa "esta función devuelve una cadena". Las anotaciones del tipo de retorno son especialmente útiles cuando el tipo de retorno no es obvio por el nombre de la función:

python
def is_adult(age: int) -> bool:
    """Comprobar si alguien es adulto (18 o más)."""
    return age >= 18
 
# Usage
adult = is_adult(25)
print(adult)  # Output: True

Sin mirar la implementación, sabes inmediatamente que esta función devuelve un valor booleano.

Juntándolo todo: una función completa

La mayoría de las funciones tendrán tanto anotaciones de parámetros como de tipo de retorno. Así es como se ve una función totalmente anotada:

python
def calculate_discount(price: float, discount_percent: float) -> float:
    """Calcular el precio con descuento."""
    discount_amount = price * (discount_percent / 100)
    return price - discount_amount
 
# Usage
original_price = 100.0
discount = 20.0
final_price = calculate_discount(original_price, discount)
print(f"Final price: ${final_price:.2f}")  # Output: Final price: $80.00

Esta firma de la función te dice todo lo que necesitas saber:

  • Toma dos parámetros float: price y discount_percent
  • Devuelve un valor float
  • No necesitas leer la implementación para entender cómo usar esta función

Veamos otro ejemplo con tipos diferentes:

python
def repeat_message(message: str, times: int) -> str:
    """Repetir un mensaje un número especificado de veces."""
    return message * times
 
# Usage
repeated = repeat_message("Hello! ", 3)
print(repeated)  # Output: Hello! Hello! Hello! 

Las sugerencias de tipo dejan claro que pasas una cadena y un entero, y obtienes de vuelta una cadena.

Trabajar con valores predeterminados

Cuando un parámetro tiene un valor predeterminado, coloca la sugerencia de tipo entre el nombre del parámetro y el valor predeterminado:

python
def create_greeting(name: str, formal: bool = False) -> str:
    """Crear un mensaje de saludo."""
    if formal:
        return f"Good day, {name}."
    return f"Hi, {name}!"
 
# Usage
print(create_greeting("Alice"))              # Output: Hi, Alice!
print(create_greeting("Bob", formal=True))   # Output: Good day, Bob.

La sintaxis formal: bool = False significa que "formal es un booleano con un valor predeterminado de False."

Puedes tener múltiples parámetros con valores predeterminados, todos anotados:

python
def format_price(amount: float, currency: str = "USD", decimals: int = 2) -> str:
    """Formatear un precio con símbolo de moneda."""
    if currency == "USD":
        symbol = "$"
    elif currency == "EUR":
        symbol = "€"
    else:
        symbol = currency
    
    return f"{symbol}{amount:.{decimals}f}"
 
# Usage
print(format_price(99.99))                          # Output: $99.99
print(format_price(99.99, "EUR"))                   # Output: €99.99
print(format_price(99.995, "USD", 3))               # Output: $99.995

Cada parámetro muestra claramente su tipo y valor predeterminado, haciendo que la función sea fácil de entender y usar.

Caso especial: funciones que no devuelven valores

Algunas funciones solo realizan acciones (como imprimir o escribir en un archivo) sin devolver un valor. Para dejar claro que estas funciones no devuelven nada, usa -> None:

python
def print_report(title: str, data: list) -> None:
    """Imprimir un informe con formato."""
    print(f"=== {title} ===")
    for item in data:
        print(f"  - {item}")
    # Sin sentencia return, así que devuelve None implícitamente
 
# Usage
print_report("Sales Data", [100, 150, 200])

Output:

=== Sales Data ===
  - 100
  - 150
  - 200

La anotación -> None indica explícitamente que esta función no devuelve un valor significativo.

¿Por qué usar -> None?

  • Claridad: deja explícita tu intención: esta función es para acciones, no para resultados
  • Soporte del IDE: tu IDE puede advertirte si intentas usar accidentalmente el valor devuelto

42.4) Anotaciones simples de variables

Aunque las sugerencias de tipo se usan con más frecuencia con funciones, también puedes anotar variables. Veamos cómo funciona esto y cuándo es realmente útil.

Sintaxis básica de anotación de variables

Para anotar una variable, usa la misma sintaxis con dos puntos que con los parámetros de funciones:

python
# Anotar variables
name: str = "Alice"
age: int = 30
height: float = 5.7
is_student: bool = True
 
print(f"{name} is {age} years old")  # Output: Alice is 30 years old

La sintaxis name: str = "Alice" significa "la variable name es una cadena y tiene el valor 'Alice'." La anotación no cambia cómo funciona la variable: es puramente informativa.

Las anotaciones de variables a menudo se omiten

En la práctica, las anotaciones de variables rara vez se usan. La razón es simple: Python puede inferir el tipo a partir del valor, así que las anotaciones suelen ser redundantes:

python
# Estas anotaciones son innecesarias
name: str = "Alice"  # Obviamente una cadena
count: int = 0  # Obviamente un int
prices: list = [10.99, 5.50]  # Obviamente una lista
settings: dict = {}  # Obviamente un dict
 
# En su lugar, escribe esto
name = "Alice"
count = 0
prices = [10.99, 5.50]
settings = {}

Cuando escribes name = "Alice", tanto tú como tu IDE sabéis inmediatamente que es una cadena. La anotación no añade información útil.

En el código Python del mundo real, rara vez verás anotaciones de variables. Esto es normal y esperable. Las anotaciones de funciones son mucho más importantes y comunes.

El único caso útil: declarar variables antes de asignarlas

Hay una situación en la que las anotaciones de variables son genuinamente útiles: cuando necesitas declarar una variable antes de asignarle un valor.

python
def calculate_statistics(numbers: list) -> dict:
    """Calcular estadísticas básicas a partir de una lista de números."""
    # Declarar variables antes de usarlas
    total: float
    count: int
    average: float
    
    # Ahora asignar valores
    total = sum(numbers)
    count = len(numbers)
    average = total / count if count > 0 else 0.0
    
    return {
        "total": total,
        "count": count,
        "average": average
    }
 
# Usage
result = calculate_statistics([10, 20, 30, 40])
print(f"Average: {result['average']}")  # Output: Average: 25.0

Sin anotaciones, no puedes declarar una variable sin asignarle también un valor. Las anotaciones te permiten especificar los tipos por adelantado, lo que puede hacer que la estructura del código sea más clara.

Este es el principal caso de uso práctico de las anotaciones de variables.

Recuerda: las variables pueden reasignarse a tipos diferentes

Incluso con una anotación de tipo, puedes reasignar una variable a un tipo diferente:

python
# Comenzar con una cadena
value: str = "hello"
print(value)  # Output: hello
 
# Reasignar a un tipo diferente: Python permite esto
value = 42
print(value)  # Output: 42
 
# Otro cambio de tipo: aún se permite
value = [1, 2, 3]
print(value)  # Output: [1, 2, 3]

Tu IDE o un verificador de tipos estático te advertirá sobre estos cambios de tipo, pero Python en sí no los impide. Las sugerencias de tipo te guían hacia la consistencia, pero no la imponen en tiempo de ejecución.

42.5) Manejar "None": tipos opcionales y el operador |

Uno de los patrones más comunes en Python es una función que podría devolver un valor o podría devolver None. Por ejemplo, buscar un elemento puede tener éxito (devolviendo el elemento) o fallar (devolviendo None). Las sugerencias de tipo proporcionan formas claras de expresar este patrón.

El problema: funciones que podrían devolver None

Considera esta función que busca un usuario:

python
def find_user_by_email(email: str) -> dict:
    """Encontrar un usuario por dirección de correo."""
    users = [
        {"name": "Alice", "email": "alice@example.com"},
        {"name": "Bob", "email": "bob@example.com"}
    ]
    
    for user in users:
        if user["email"] == email:
            return user
    
    return None  # ¡Incompatibilidad de tipos! Esto contradice la sugerencia -> dict
 
# Usage
user = find_user_by_email("alice@example.com")
if user:
    print(f"Found: {user['name']}")  # Output: Found: Alice
else:
    print("User not found")

La sugerencia de tipo -> dict es engañosa porque la función puede devolver None. Un verificador de tipos estático te advertiría que devolver None no coincide con el tipo de retorno declarado dict.

Solución: usar el operador | para tipos opcionales

Python 3.10 introdujo el operador | para sugerencias de tipo, que significa "o". Puedes usarlo para indicar que una función podría devolver un tipo u otro:

python
def find_user_by_email(email: str) -> dict | None:
    """Encontrar un usuario por dirección de correo. Devuelve None si no se encuentra."""
    users = [
        {"name": "Alice", "email": "alice@example.com"},
        {"name": "Bob", "email": "bob@example.com"}
    ]
    
    for user in users:
        if user["email"] == email:
            return user
    
    return None
 
# Usage
user = find_user_by_email("alice@example.com")
if user:
    print(f"Found: {user['name']}")  # Output: Found: Alice
 
missing = find_user_by_email("charlie@example.com")
if missing is None:
    print("User not found")  # Output: User not found

La sugerencia de tipo -> dict | None significa "esta función devuelve o bien un diccionario o bien None." Esto describe con precisión el comportamiento de la función.

Nota: En código Python más antiguo (antes de 3.10), podrías ver Optional[dict] del módulo typing en lugar de dict | None. Significan lo mismo, pero | es la sintaxis moderna y preferida.

Usar | con múltiples tipos

Puedes usar | para indicar más de dos tipos posibles:

python
def parse_value(text: str) -> int | float | None:
    """Convertir una cadena en un número. Devuelve None si el análisis falla."""
    try:
        # Intentar analizar como entero primero
        if '.' not in text:
            return int(text)
        # En caso contrario, analizar como float
        return float(text)
    except ValueError:
        return None
 
# Usage
print(parse_value("42"))      # Output: 42 (int)
print(parse_value("3.14"))    # Output: 3.14 (float)
print(parse_value("invalid")) # Output: None

La sugerencia de tipo -> int | float | None significa que la función puede devolver un entero, un float o None.

Comprobar None: mejores prácticas

Cuando una función puede devolver None, comprueba siempre None antes de usar el resultado. De lo contrario, te arriesgas a errores al intentar usar None como si fuera el tipo esperado:

python
def get_user_age(user_id: int) -> int | None:
    """Obtener la edad del usuario. Devuelve None si no se encuentra el usuario."""
    users = {1: 25, 2: 30, 3: 35}
    return users.get(user_id)
 
# Comprueba siempre None antes de usar el valor
age = get_user_age(1)
if age is not None:
    print(f"User is {age} years old")  # Output: User is 25 years old
    if age >= 18:
        print("User is an adult")  # Output: User is an adult
else:
    print("User not found")
 
# Para usuarios inexistentes
age = get_user_age(999)
if age is None:
    print("User not found")  # Output: User not found

La clave es usar if age is not None: o if age is None: para comprobar explícitamente antes de usar el valor.

Parámetros opcionales con | None

También puedes usar | con parámetros, a menudo combinado con valores predeterminados:

python
def format_name(first: str, middle: str | None = None, last: str = "") -> str:
    """Formatear un nombre completo. El segundo nombre es opcional."""
    if middle and last:
        return f"{first} {middle} {last}"
    elif last:
        return f"{first} {last}"
    return first
 
# Usage
print(format_name("John", "Q", "Doe"))    # Output: John Q Doe
print(format_name("Jane", None, "Smith")) # Output: Jane Smith
print(format_name("Prince"))              # Output: Prince

La sugerencia de tipo middle: str | None = None indica que middle puede ser una cadena o None, con None como valor predeterminado. Este es un patrón común para parámetros opcionales.

42.6) Leer sugerencias de tipo comunes: list, dict, tuple

A medida que leas código Python escrito por otras personas, te encontrarás con sugerencias de tipo para colecciones como listas, diccionarios y tuplas. Python moderno proporciona formas claras de especificar no solo que algo es una lista, sino qué tipo de elementos contiene la lista.

Nota: La sintaxis mostrada aquí (list[int], dict[str, int], etc.) funciona en Python 3.9+. En código más antiguo, podrías ver List[int] y Dict[str, int] (con mayúscula) del módulo typing; funcionan de la misma manera.

Sugerencias básicas de tipo para colecciones

Las sugerencias de tipo más simples para colecciones solo especifican el tipo de colección:

python
def print_items(items: list) -> None:
    """Imprimir todos los elementos de una lista."""
    for item in items:
        print(item)
 
def get_user_settings() -> dict:
    """Obtener la configuración del usuario como un diccionario."""
    return {"theme": "dark", "notifications": True}
 
def get_position() -> tuple:
    """Obtener la posición x, y."""
    return (10, 20)

Estas sugerencias te dicen el tipo de colección, pero no qué hay dentro.

Listas: especificar tipos de elementos

Para especificar qué tipo de elementos contiene una lista, usa corchetes:

python
def calculate_total(prices: list[float]) -> float:
    """Calcular el total de todos los precios."""
    return sum(prices)
 
# Usage
total = calculate_total([10.99, 5.50, 3.25])
print(f"Total: ${total:.2f}")  # Output: Total: $19.74

La sugerencia de tipo list[float] significa "una lista que contiene floats." Esto es más informativo que solo list.

Aquí tienes otro ejemplo con cadenas:

python
def format_names(names: list[str]) -> str:
    """Formatear una lista de nombres como una cadena separada por comas."""
    return ", ".join(names)
 
# Usage
students = ["Alice", "Bob", "Charlie"]
print(format_names(students))  # Output: Alice, Bob, Charlie

La sugerencia de tipo list[str] significa "una lista que contiene cadenas."

Diccionarios: especificar tipos de clave y valor

Para diccionarios, especifica tanto el tipo de clave como el tipo de valor:

python
def get_student_grades() -> dict[str, int]:
    """Obtener nombres de estudiantes mapeados a sus calificaciones."""
    return {
        "Alice": 95,
        "Bob": 87,
        "Charlie": 92
    }
 
# Usage
grades = get_student_grades()
for name, grade in grades.items():
    print(f"{name}: {grade}")

Output:

Alice: 95
Bob: 87
Charlie: 92

La sugerencia de tipo dict[str, int] significa "un diccionario con claves de cadena y valores enteros."

Aquí tienes un ejemplo donde los valores pueden ser de múltiples tipos:

python
def get_user_data(user_id: int) -> dict[str, str | int]:
    """Obtener datos del usuario. Los valores pueden ser cadenas o enteros."""
    return {
        "name": "Alice",
        "email": "alice@example.com",
        "age": 30,
        "id": 12345
    }
 
# Usage
user = get_user_data(1)
print(f"{user['name']} is {user['age']} years old")  # Output: Alice is 30 years old

La sugerencia de tipo dict[str, str | int] significa "un diccionario con claves de cadena y valores que son o bien cadenas o bien enteros."

Tuplas: longitud fija y variable

Las tuplas son diferentes de las listas porque a menudo tienen una estructura fija. Puedes especificar el tipo de cada posición:

python
def get_user_info(user_id: int) -> tuple[str, int, bool]:
    """
    Obtener información del usuario como una tupla.
    Devuelve: (name, age, is_active)
    """
    return ("Alice", 30, True)
 
# Usage
name, age, active = get_user_info(1)
print(f"{name}, {age}, active: {active}")  # Output: Alice, 30, active: True

La sugerencia de tipo tuple[str, int, bool] significa "una tupla con exactamente tres elementos: una cadena, un entero y un booleano, en ese orden."

Para tuplas de longitud variable con elementos del mismo tipo, usa puntos suspensivos (...):

python
# Tupla de longitud fija: exactamente 2 floats
def get_2d_point() -> tuple[float, float]:
    """Obtener coordenadas 2D (x, y)."""
    return (10.5, 20.3)
 
# Tupla de longitud variable: cualquier cantidad de floats
def get_coordinates() -> tuple[float, ...]:
    """Obtener coordenadas. Puede ser 2D, 3D o cualquier dimensión."""
    return (10.5, 20.3, 15.7)  # 3D en este caso
 
# Usage
point = get_2d_point()
coords = get_coordinates()
print(f"2D point: {point}")       # Output: 2D point: (10.5, 20.3)
print(f"Coordinates: {coords}")   # Output: Coordinates: (10.5, 20.3, 15.7)

La sugerencia de tipo tuple[float, ...] significa "una tupla que contiene cualquier cantidad de floats." El ... significa "cualquier cantidad de este tipo."

Colecciones anidadas

Puedes anidar sugerencias de tipo para estructuras de datos complejas. Empecemos con un ejemplo simple:

python
def get_scores_by_student() -> dict[str, list[int]]:
    """Obtener puntuaciones de exámenes para cada estudiante."""
    return {
        "Alice": [95, 87, 92],
        "Bob": [88, 91, 85],
        "Charlie": [90, 88, 94]
    }
 
# Usage
scores = get_scores_by_student()
for name, tests in scores.items():
    average = sum(tests) / len(tests)
    print(f"{name}: {average:.1f}")

Output:

Alice: 91.3
Bob: 88.0
Charlie: 90.7

La sugerencia de tipo dict[str, list[int]] significa "un diccionario con claves de cadena y valores de lista-de-enteros."

Aquí tienes un ejemplo más complejo:

python
def get_student_records() -> list[dict[str, str | int]]:
    """Obtener una lista de registros de estudiantes."""
    return [
        {"name": "Alice", "age": 20, "major": "CS"},
        {"name": "Bob", "age": 21, "major": "Math"},
        {"name": "Charlie", "age": 19, "major": "Physics"}
    ]
 
# Usage
students = get_student_records()
for student in students:
    print(f"{student['name']}, {student['age']}, {student['major']}")

Output:

Alice, 20, CS
Bob, 21, Math
Charlie, 19, Physics

La sugerencia de tipo list[dict[str, str | int]] significa "una lista de diccionarios, donde cada diccionario tiene claves de cadena y valores que son o bien cadenas o bien enteros."

Leer sugerencias de tipo: una referencia rápida

Cuando te encuentres sugerencias de tipo en código, así es como leerlas:

Colecciones:

  • list[int] - "una lista de enteros"
  • dict[str, float] - "un diccionario con claves de cadena y valores float"
  • tuple[str, int] - "una tupla con exactamente dos elementos: una cadena y luego un entero"
  • tuple[float, ...] - "una tupla que contiene cualquier cantidad de floats"

Opcional y múltiples tipos:

  • int | None - "un entero o None"
  • str | int | float - "una cadena, un entero o un float"

Anidadas:

  • list[dict[str, int]] - "una lista de diccionarios (cada dict tiene claves de cadena y valores enteros)"
  • dict[str, list[float]] - "un diccionario con claves de cadena y valores de lista-de-floats"

Nota: En código más antiguo (Python < 3.10), podrías ver Union[int, str] en lugar de int | str, o Optional[int] en lugar de int | None. Significan lo mismo.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai