¿Cómo debemos escribir un objeto callable con propiedades adicionales?
Como ejemplo de juguete, usemos la secuencia de Fibonacci:
def fib(n: int) -> int:
if n < 2:
return 1
return fib(n - 2) + fib(n - 1)
Por supuesto, esto bloqueará la computadora si intentamos:
print(fib(100))
Así que decidimos agregar memoización. Para mantener la lógica de fib
clara, decidimos no cambiar fib
y en su lugar agregar memoización a través de un decorador:
from typing import Callable
from functools import wraps
def remember(f: Callable[[int], int]) -> Callable[[int], int]:
@wraps(f)
def wrapper(n: int) -> int:
if n not in wrapper.memory:
wrapper.memory[n] = f(n)
return wrapper.memory[n]
wrapper.memory = dict[int, int]()
return wrapper
@remember
def fib(n: int) -> int:
if n < 2:
return 1
return fib(n - 2) + fib(n - 1)
Ahora no hay problema si ejecutamos:
print(fib(100))
573147844013817084101
Sin embargo, mypy
se queja de que “Callable[[int], int]” no tiene el atributo “memory”, lo cual tiene sentido, y usualmente querría esta queja si intentara acceder a una propiedad que no es parte del tipo declarado…
Entonces, ¿cómo deberíamos usar typing
para indicar que wrapper
, aunque es un Callable
, también tiene la propiedad memory
?
davy.ai
Podemos utilizar el módulo
typing
para indicar quewrapper
tiene la propiedadmemory
al definir un tipo personalizado utilizandotyping.Callable
ytyping.Dict
, y combinándolos utilizando la clasetyping.Protocol
. Aquí te mostramos cómo podemos modificar el código para lograrlo:En este código modificado, definimos un nuevo protocolo
MemoizeWrapper
que extiendeCallable
. El protocolo requiere que la funciónwrapper
tenga una propiedadmemory
con un diccionario que mapea claves de tipoint
a valores de tipoint
. Luego, utilizamos este tipo personalizado como tipo de retorno de la funciónremember
.Al hacer esto,
mypy
comprende que la funciónwrapper
tiene una propiedadmemory
y ya no reporta un error sobre el atributo faltante.