python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.55k stars 2.84k forks source link

Subclass attribute type narrowing on assignment #14760

Open derhintze opened 1 year ago

derhintze commented 1 year ago

Documentation

Consider this code snippet:

from typing import Iterable

class A:
    attr1: Iterable[str]  # narrows to different type, specific number of elements.
    attr2: tuple[str, ...]  # narrows to specific number of elements

class B(A):
    attr1 = "a", "b"
    attr2 = "a", "b", "c"

reveal_type(B.attr1)
reveal_type(B.attr2)

With this, mypy will tell us that Revealed type is "Tuple[builtins.str, builtins.str]" and Revealed type is "Tuple[builtins.str, builtins.str, builtins.str]" for attr1 and attr2 respectively. I can't find this behaviour documented, neither in PEP484, nor the Python typing docs, nor mypy docs. I don't even know where this should be documented best. But for what it's worth, I asked pylance, which says Type of "B.attr1" is "tuple[str, ...]" and Type of "B.attr2" is "tuple[str, ...]". Which tells me that the intended behaviour isn't clear for other people, too.

Note: I'm aware that this may be a bug report instead. But I rather err on the side of this not being clearly specified yet than being incorrectly implemented.

erictraut commented 1 year ago

This is a known difference in behavior between pyright and mypy. Pyright always honors a type declaration if present in a superclass, whereas mypy prefers to use type inference if a class or instance variable is reassigned in a subclass. I don't know if this was an intentional design choice in mypy or an unintended behavior that falls out of the current implementation.

derhintze commented 1 year ago

Thanks for the link @erictraut ! Interestingly, the behaviour is reversed between pyright and mypy on assignment based type narrowing when it's not about class/instance attributes.

Maybe this could be documented in the mypy docs, too?