python / python/mypy

MyPy fails type narrowing on `not issubclass(X, type[T]): raise`

Offen
#19,917 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

bug
Vorherrschende Sprache
Python
Sterne
20.6k
Forks
3.3k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

Bug Report

MyPy fails to narrow types using issubclass, and expects an isinstance check which is wrong.

MyPy fails type narrowing on not issubclass(X, type[T]): raise and complains about returning X() later for the function marked as returning T.

To Reproduce

[Mypy Playground example](https://gist.github.com/mypy-play/dcd17343b289523c197a31f850eca90f)

```python from collections.abc import Iterator from typing import TYPE_CHECKING

class MySentinel:
    pass

# Works as expected
class OptionalSentinel:
    def __init__(self, value: object) -> None:
        self._value = value

    def get_value[T](self, expected: type[T]) -> Iterator[T]:
        if not isinstance(self._value, expected):
            raise TypeError

        yield self._value

# Reveals bug
class MagicIterator:
    def raw[T](self, expected: type[T]) -> Iterator[T]:
        """Get a list of repositories which accept a specific lint to run."""
        if not issubclass(MySentinel, expected):
            raise ValueError

        # if TYPE_CHECKING:
        #     # an ugly workaround to make mypy happy
        #     if not isinstance(expected, MySentinel):
        #         raise ValueError

        for project in [MySentinel(), MySentinel()]:
            yield project  # error: Incompatible types in "yield" (actual type "MySentinel", expected type "T")  [misc]
```

Expected Behavior

I expected mypy to infer that after an if not issubclass(T, X): raise block X must be subclass T and it should be ok to return X in place of T.

Since I pass a type and want to compare it to a type an isinstance check is not appropriate for MagicIterator, neiter o I think the issubclass check is the most appropriate (I would prefer to check using expected is not T to guarantee that expected is actually exactly T but ìs`i not listed in the list of supported narrowing expressions.

Actual Behavior

Mypy is only happy if I add an isinstance check, which I deem wrong since I dont pass an instance but a type to the function. Thus, if I do not put the incorrect code in a if TYPE_CHECKCING block my program crashes

src/typeTest.py:31: error: Incompatible types in "yield" (actual type "MySentinel", expected type "T")  [misc]
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: mypy 1.18.2 (compiled: yes)
  • Mypy command-line flags: none (mypy src)
  • Mypy configuration options from pyproject.toml (and other config files):
  • Python version used: 3.13.7 (uv)

** Possibly related **:

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit dem verlinkten MyPy Playground-Beispiel und vergleiche den fehlschlagenden issubclass-Narrowing-Pfad mit dem funktionierenden isinstance-Beispiel. Lies die zugehörigen Issues #10680, #19529, #9003 und #17728, bevor du die relevanten Narrowing-Tests und die Implementierung suchst; abgeschlossen ist die Aufgabe, wenn das Beispiel ohne den fehlerhaften Laufzeit-Workaround typgeprüft wird.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
devtools
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.