python / python/mypy

Use of ```not``` keyword does not always narrow out type ```None```

Offen
#17,107 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

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

Beschreibung

Bug Report

I wanted to make an item class with multiple descriptor-'variant' attributes (optional-strings). Any of these attr's could be null/empty, but AT LEAST ONE of them needs to be defined (i.e. a non-empty string). And I want a read-only property that should return the 'primary' descriptor-value (as string), or raise an err if no descriptors are defined.

In the simplified example below, I implemented this logic for two descriptor-variants, in two seemingly-equivalent ways, but for some reason MyPy yields an error in the first approach but not the second.

To Reproduce

from typing import Optional

class Item_V1:
    descriptor_variant1: Optional[str]
    descriptor_variant2: Optional[str]
    # ... followed by any number of other arbitrary attr's that we don't care about.
    
    @property
    def primary_descriptor(self) -> str:
        if not self.descriptor_variant1 and not self.descriptor_variant2:
            raise AttributeError("At least one item-descriptor variant must be defined.")
        if not self.descriptor_variant1:
            return self.descriptor_variant2
        return self.descriptor_variant1

class Item_V2:
    descriptor_variant1: Optional[str]
    descriptor_variant2: Optional[str]
    
    @property
    def primary_descriptor(self) -> str:
        if not self.descriptor_variant1:
            if not self.descriptor_variant2:
                raise AttributeError("At least one item-descriptor variant must be defined.")
            return self.descriptor_variant2
        return self.descriptor_variant1

MyPy Output

I believe that the primary_descriptor property for both Item_V1 and Item_V2 should be evaluated without errors by MyPy; however, in reality, l.13 in the Item_V1 approach yields MyPy error "Incompatible return value type (got "Optional[str]", expected "str") [return-value]", whereas the equivalent return-statement for Item_V2 (l.26) is evaluated as correct.

Your Environment

  • Python version used: 3.9 [but tested up to v3.12 using https://mypy-play.net/]
  • Mypy version used: 1.6.1 [but tested up to v1.9.0 using https://mypy-play.net/]
  • Mypy command-line flags: N/A
  • Mypy configuration options from mypy.ini (and other config files): nothing relevant

Caveats/Disclaimers

  • Whilst in theory the Item_V2 method is fine for my current scenario, in an ideal world I would prefer to be able to the use the Item_V1-type sequence, as otherwise this would lead to an n-level nested if-statement, which seems like bad style, and would become unwieldy as num. descriptor variants increases. And mostly I just don't understand why there is any difference in MyPy's behaviour...
  • Before anyone asks something of the form: why not store the descriptor variants using an Iterable/dict? - the actual use-case here is in a Django model, where each descriptor variant needs to map to a nullable/blank-able CharField. But that's not really relevant to the error state, in any case.

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

Reproduziere die Item_V1- und Item_V2-Snippets auf mypy-play.net mit den gemeldeten Python- und mypy-Versionen und verfolge anschließend das Typverengungsverhalten bei verketteten not-Prüfungen. Als erledigt gilt die Aufgabe, wenn die Abweichung bestätigt und die erwartete Behandlung durch einen Regressionstest abgedeckt ist.

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
45/100

Neue Issues direkt in Ihr Postfach

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