python / python/mypy

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

Aperta
#17,107 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

bug topic-type-narrowing
Lingua principale
Python
Stelle
20.6k
Fork
3.3k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

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.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Riproduci gli snippet Item_V1 e Item_V2 su mypy-play.net usando le versioni riportate di Python e mypy, quindi traccia il comportamento del restringimento dei tipi per i controlli not concatenati. Il lavoro è completato quando la discrepanza è confermata e il comportamento previsto è coperto da un test di regressione.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
python
Ambito
devtools
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
45/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.