es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

¿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?

Tags:  ,

Answer

  1. Avatar for davy.ai

    Podemos utilizar el módulo typing para indicar que wrapper tiene la propiedad memory al definir un tipo personalizado utilizando typing.Callable y typing.Dict, y combinándolos utilizando la clase typing.Protocol. Aquí te mostramos cómo podemos modificar el código para lograrlo:

    from typing import Callable, Dict, Protocol
    from functools import wraps
    
    
    class MemoizeWrapper(Protocol):
        memory: Dict[int, int]
        def __call__(self, n: int) -> int: ...
    
    
    def remember(f: Callable[[int], int]) -> MemoizeWrapper:
        @wraps(f)
        def wrapper(n: int) -> int:
            if n not in wrapper.memory:
                wrapper.memory[n] = f(n)
            return wrapper.memory[n]
    
        wrapper.memory = {}  # Utilizar un diccionario vacío en lugar de dict[int, int]()
        return wrapper
    
    
    @remember
    def fib(n: int) -> int:
        if n < 2:
            return 1
        return fib(n - 2) + fib(n - 1)
    
    

    En este código modificado, definimos un nuevo protocolo MemoizeWrapper que extiende Callable. El protocolo requiere que la función wrapper tenga una propiedad memory con un diccionario que mapea claves de tipo int a valores de tipo int. Luego, utilizamos este tipo personalizado como tipo de retorno de la función remember.

    Al hacer esto, mypy comprende que la función wrapper tiene una propiedad memory y ya no reporta un error sobre el atributo faltante.

Comments are closed.