En el siguiente ejemplo de código tenemos un dict con contiene una instancia de una clase base abstracta y una instancia de su subtipo.

from typing import Dict, Union


class Base:
    def __init__(self):
        self.x = 0


class Sub(Base):
    def __init__(self):
        super().__init__()
        self.y = 1


d: Dict[str, Base] = {
    'base': Base(),
    'sub': Sub()
}

print(d['sub'].y)

Acceder a una variable de instancia de subtipo conduce a la advertencia de linter Unresolved attribute reference 'y' for class 'Base' en Pycharm.

Verificar este ejemplo con mypy genera un error:

error: Item "Base" of "Union[Base, Sub]" has no attribute "y"

Cambiar el código a

d: Dict[str, Union[Base, Sub]] = {
    'base': Base(),
    'sub': Sub()
}

Corrige la advertencia de linter en Pycharm, pero el error aún se genera en mypy.

Del mypy docs sé que "es muy mutable las colecciones genéricas son invariantes ". Por lo tanto, ¿supongo que dict es "invariante"?

¿Significa esto que no es posible tener un dict con instancias de diferentes subclases en mypy? Si es así, ¿se puede cambiar este código de alguna manera para que pase mypy?

Como las claves no se conocen antes del tiempo de ejecución, ¿supongo que TypedDict no es una opción?

0
felixinho 5 dic. 2019 a las 05:52

2 respuestas

Acceder a un campo desde el valor cuando su tipo puede ser el que no tiene ese campo (Base en su caso) es un error desde la perspectiva de mypy (y también desde el mío).

Debería repensar sus clases y la jerarquía de herencia o agregar isinstance comprobaciones como

... # same as before
value = d['sub']
if isinstance(value, Sub):
    print(value.y)

Y mypy dirá algo como

Success: no issues found in 1 source file
1
Azat Ibrakov 6 dic. 2019 a las 19:01

Puede usar cast para decirle a mypy que sí, usted promete que d['sub'] será una instancia de Sub (no solo una subclase no especificada de Base), que tendrá un atributo y.

print(typing.cast(Sub, d['sub']).y)
1
chepner 6 dic. 2019 a las 19:15