A2.B Restricciones y variación¶
Restringir el tipo¶
Si una función genérica necesita hacer algo con sus valores, como compararlos o sumarlos, tiene que saber que existe esa operación. Se lo cuentas con una restricción.
En Python se restringe con bound= (un tipo máximo) o con una lista cerrada de tipos: TypeVar("Numero", int, float). Para «cualquier tipo que sepa compararse» se declara un Protocol con el método __lt__. Es lo que lee mypy; al ejecutar, nada de esto se comprueba.
# Restricciones: se anotan con "bound" (un tipo máximo) o con una lista cerrada de tipos.
# Igual que en cualquier anotación de Python, las comprueba mypy o el editor, no el intérprete.
from typing import Callable, Protocol, TypeVar
class Comparable(Protocol):
"""Cualquier tipo que sepa decir si es menor que otro (int, float, str...)."""
def __lt__(self, otro, /) -> bool: ...
C = TypeVar("C", bound=Comparable)
Numero = TypeVar("Numero", int, float) # solo int o float
T = TypeVar("T")
def maximo(lista: list[C]) -> C:
mayor = lista[0]
for x in lista:
if mayor < x:
mayor = x
return mayor
def sumar(lista: list[Numero]) -> float:
return float(sum(lista))
# Sin restricción: acepta cualquier T y una función que lo examina
def contar_si(lista: list[T], cumple: Callable[[T], bool]) -> int:
return sum(1 for x in lista if cumple(x))
print(f"maximo de [3, 9, 4]: {maximo([3, 9, 4])}")
print(f"maximo de [pera, manzana, uva]: {maximo(['pera', 'manzana', 'uva'])}")
print(f"suma de [1, 2, 3]: {sumar([1, 2, 3])}")
print(f"suma de [0.5, 0.25]: {sumar([0.5, 0.25])}")
print(f"pares en [1..6]: {contar_si([1, 2, 3, 4, 5, 6], lambda n: n % 2 == 0)}")
# maximo([object()]) lo marcaría mypy: object no define __lt__
Salida:
maximo de [3, 9, 4]: 9
maximo de [pera, manzana, uva]: uva
suma de [1, 2, 3]: 6.0
suma de [0.5, 0.25]: 0.75
pares en [1..6]: 3
| Función | Qué demuestra |
|---|---|
maximo |
Funciona con enteros y con textos, pero solo con tipos que se pueden comparar |
sumar |
Una restricción a números: acepta enteros y decimales |
contarSi |
Sin restricción: no necesita saber nada de T, porque delega en la función que recibe |
¿Una lista de enteros es una lista de números?¶
Parece que sí, pero depende de si la lista se puede modificar. Cada lenguaje lo resuelve distinto:
Para el comprobador de tipos, list es invariante (una list[int] no vale donde se espera list[float], porque podrías añadir un float), mientras que Sequence y tuple solo se leen y son covariantes. Al ejecutar no hay diferencia: es otra comprobación que hace mypy, no el intérprete.
Cómo decidir en Python
Pregúntate si tu función solo lee de la colección, solo escribe o hace las dos cosas. Si solo lee, acepta el tipo más amplio que puedas; si hace las dos, el tipo tiene que ser exacto.
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
Restringir demasiado (por ejemplo, pedir ArrayList cuando vale cualquier lista) |
Pide la interfaz más general que necesites |
| Llamar a un método que la restricción no garantiza | Añade la restricción que lo garantice, o recibe una función como parámetro |
| Querer escribir en una colección que se ha recibido como «solo lectura» | Recibe una colección modificable del tipo exacto |
Para practicar¶
Los ejercicios A2.3 y A2.5 usan una restricción y una función como criterio. Para ver la misma idea en los otros lenguajes: Pasar de uno a otro.