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.

Obtener instancias de subclases con métodos abstractos en Python MyPy

Estoy tratando de obtener una lista de instancias de clase para clases que heredan de una clase padre en Python mientras uso MyPy. Tengo las siguientes clases de Python:

animal.py

from abc import abstractmethod
from typing import Dict

class Animal():

    @property
    @abstractmethod
    def id(self) -> str:
        pass

    @property
    @abstractmethod
    def sound(self) -> str:
        pass

    def get_all_animals(cls) -> Dict[str, 'Animal']:
        animals: Dict[str, Animal] = {}
        for _class in Animal.__subclasses__():
            animal = _class()
            animals[animal.id] = animal
        return animals

cat.py

from test.animal import Animal

class Cat(Animal):

    @property
    def id(self) -> str:
        return "Cat"

    @property
    def sound(self) -> str:
        return "Meow"

dog.py

from test.animal import Animal

class Dog(Animal):

    @property
    def id(self) -> str:
        return "Dog"

    @property
    def sound(self) -> str:
        return "Woof"

Sin embargo, en animal.py en la línea animal = _class(), estoy obteniendo el siguiente error:

No se puede instanciar la clase abstracta "Animal" con los atributos abstractos "id" y "sound"

Por lo que entiendo, no puedo instanciar instancias de Animal porque hay métodos abstractos. Pero en este caso particular, solo estoy instanciando instancias de clases hijas que tienen esos métodos abstractos. ¿Hay alguna manera de decirle a MyPy que Animal.__subclasses__() devuelve tipos con sus métodos abstractos implementados?

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Para indicarle a MyPy que Animal.__subclasses__() devuelve tipos con sus métodos abstractos implementados, puedes utilizar anotaciones de tipo y sugerencias de tipo en animal.py. Específicamente, puedes modificar el método get_all_animals de la siguiente manera:

    from typing import Type, Dict
    
    class Animal():
    
        @property
        def id(self) -> str:
            raise NotImplementedError
    
        @property
        def sound(self) -> str:
            raise NotImplementedError
    
        @classmethod
        def get_all_animals(cls: Type['Animal']) -> Dict[str, 'Animal']:
            animals: Dict[str, Animal] = {}
            for subclass in cls.__subclasses__():
                animal = subclass()
                assert isinstance(animal, Animal)
                animals[animal.id] = animal
            return animals
    

    Los cambios realizados incluyen:

    • Agregar anotaciones de tipo al parámetro cls del método get_all_animals, lo cual indica que espera un objeto Type para la clase (en este caso, Animal).
    • Agregar anotaciones de tipo al valor de retorno del método get_all_animals, lo cual indica que devuelve un diccionario con claves de tipo str y valores de tipo Animal.
    • Verificar explícitamente que el objeto animal instanciado sea una instancia de Animal utilizando la función isinstance.
    • Cambiar las definiciones de los métodos abstractos para que generen un NotImplementedError en lugar de utilizar la palabra clave pass. Esto hace más claro que los métodos deben ser implementados por las clases derivadas.

    Al hacer esto, MyPy podrá inferir correctamente los tipos de las instancias devueltas por get_all_animals y no generará el TypeError en la línea animal = _class().

Comments are closed.