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.

Python typing no acepta subclases

Tengo 3 clases:

@dataclass(frozen=True)
class Parent:
    field_a: int

@dataclass(frozen=True)
class Child(Parent):
    field_b: int

class Wrapper(NamedTuple):
    object_a: Dict[int, Parent]
    object_b: Dict[int, Parent]

Según esta pregunta de StackOverflow, parece que debería poder reemplazar las instancias de Parent por Child en Wrapper. Sin embargo, cuando estoy escribiendo una función como esta:

def get_wrapper() -> Wrapper:
    return Wrapper(object_a={1: Child(1,2)}, object_b={2: Child(2,3)})

pyre me da algo como “Expected Dict[int, Parent] for 1st parameter object_a to call Wrapper.init but got Dict[int, Child]”.

¿Alguien podría explicar por qué el uso de una subclase no funciona en este caso y cómo puedo solucionarlo?

EDIT: Parece que he simplificado demasiado el problema en el ejemplo que di. Mi función get_wrapper en realidad estaba haciendo algo como esto:

def get_child() -> Child:
    return Child(1,2)
def get_wrapper() -> Wrapper:
    child_a = get_child()
    child_b = get_child()
    return Wrapper(object_a={1: child_a}, object_b={2: child_b})

Arreglé los errores de tipo cambiando el tipo de retorno de get_child para que sea Parent.

Tags:  ,

Answer

  1. Avatar for davy.ai

    El problema surge debido a que aunque Child es una subclase de Parent, Dict[int, Child] no es una subclase de Dict[int, Parent]. Por lo tanto, no puedes simplemente reemplazar las instancias de Parent con Child en Wrapper.

    Para resolver esto, puedes usar TypeVar para crear una variable de tipo genérico que puede ser reemplazada por Parent o Child. Aquí tienes un ejemplo:

    T = TypeVar('T', bound=Parent)
    
    @dataclass(frozen=True)
    class Wrapper(NamedTuple):
        object_a: Dict[int, T]
        object_b: Dict[int, T]
    
    def get_wrapper() -> Wrapper:
        return Wrapper(object_a={1: Child(1,2)}, object_b={2: Child(2,3)})
    

    Con esta implementación, T es un marcador de posición tanto para Parent como para Child. El argumento bound=Parent asegura que T debe ser una subclase de Parent.

    En la definición de la clase Wrapper, usamos T como el tipo tanto para object_a como para object_b. Esto significa que puedes pasar diccionarios que contengan instancias de Parent o Child, siempre y cuando sean de tipo Dict[int, T] (por ejemplo, Dict[int, Parent] o Dict[int, Child]).

    Finalmente, en get_wrapper(), puedes crear instancias de Child y pasarlas directamente a los diccionarios. El sistema de tipos inferirá que T debe ser sustituido por el tipo del objeto que estás pasando.

    Ten en cuenta que si estás utilizando Python 3.10 o posterior, puedes usar la sintaxis incorporada list[t] en lugar de definir un TypeVar:

    @dataclass(frozen=True)
    class Wrapper(NamedTuple):
        object_a: dict[int, Parent | Child]
        object_b: dict[int, Parent | Child]
    
    def get_wrapper() -> Wrapper:
        return Wrapper(object_a={1: Child(1,2)}, object_b={2: Child(2,3)})
    

    Aquí, Parent | Child es un tipo de unión que puede aceptar tanto Parent como Child como tipos válidos. Esta es una alternativa más simple a utilizar TypeVar.

Comments are closed.