Tengo una superclase que tiene un método compartido por sus subclases. Sin embargo, este método debería devolver un objeto con un tipo definido en la subclase. Me gustaría que el tipo de retorno para el método esté anotado estáticamente (no un tipo dinámico) para que el código que usa las subclases pueda beneficiarse de la verificación de tipo mypy en los valores de retorno. Pero no quiero tener que redefinir el método común en la subclase solo para proporcionar su anotación de tipo. ¿Es esto posible con anotaciones de tipo python y mypy?

Algo como esto:

from typing import Type

class AbstractModel:
    pass

class OrderModel(AbstractModel):
    def do_order_stuff():
        pass

class AbstractRepository:
    model: Type[AbstractModel]

    def get(self) -> model:
        return self.model()

class OrderRepository(AbstractRepository):
    model = OrderModel


repo = OrderRepository()
order = repo.get()

# Type checkers (like mypy) should recognize that this is valid
order.do_order_stuff()

# Type checkers should complain about this; because `OrderModel`
# does not define `foo`
order.foo()

El movimiento complicado aquí es que get() está definido en la superclase AbstractRepository, que aún no conoce el tipo de model. (Y la anotación -> model falla, ya que el valor de model aún no se ha especificado).

El valor de model lo especifica la subclase, pero la subclase no (re) define get() para proporcionar la anotación. Parece que esto debería ser analizable estáticamente; aunque es un poco complicado, ya que requeriría que el analizador estático rastree la referencia model de la superclase a la subclase.

¿Alguna forma de lograr una implementación de superclase compartida y un tipo de retorno de subclase preciso?

1
user85461 21 ene. 2021 a las 22:19

1 respuesta

La mejor respuesta

Defina AbstractRepository como una clase genérica.

from typing import TypeVar, Generic, Type, ClassVar


T = TypeVar('T')

class AbstractRespotitory(Generic[T]):
    model: ClassVar[Type[T]]

    @classmethod
    def get(cls) -> T:
        return cls.model()

(get solo hace uso de un atributo de clase, por lo que puede, y posiblemente debería, ser un método de clase).

3
chepner 22 ene. 2021 a las 13:33