python / python/mypy

Collapsed Optionals in TypeVar cause weird error messages

Offen
#8,478 2 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

bug false-positive priority-1-normal topic-type-variables
Vorherrschende Sprache
Python
Sterne
20.6k
Forks
3.3k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

At a minimum, I'm reporting a bug in an error message.

I have a function, run_tasks, which takes a list of tasks and a worker function, and executes the worker function on almost all the tasks. For some of them, run_tasks will decide to not execute the task.

run_tasks returns List[Optional[Result]] where Result is a TypeVar.

This works until the worker function's returns Optional[x] where Result is now Optional[x] and run_tasks is asked to return List[Optional[Optional[x]]].

Example code:

from typing import (
    TypeVar,
    List,
    Callable,
    Optional,
)

Task = TypeVar("Task")
Result = TypeVar("Result")


def run_tasks(
    tasks: List[Task], worker_fun: Callable[[Task], Result]
) -> List[Optional[Result]]:
    results: List[Optional[Result]] = []

    toggle: bool = True
    todo: Task
    for todo in tasks:
        toggle = not toggle

        if toggle:
            work_result: Result
            work_result = worker_fun(todo)
            results.append(work_result)
        else:
            results.append(None)

    return results


def test() -> None:
    def worker(d: int) -> Optional[int]:
        if d % 2 == 0:
            return None
        else:
            return d + 1

    output: List[Optional[Optional[int]]] = run_tasks(
        [1, 2, 2, 1], worker,
    )
    from pprint import pprint

    pprint(output)
    assert output == [None, None, None, 2]


test()

I get the error message: error: Argument 2 to "run_tasks" has incompatible type "Callable[[int], Optional[int]]"; expected "Callable[[int], int]"

I understand Python can't really represent Optional[Optional[int]]. The problem is in the error message, I think. This code does work correctly, and validates:

def test2() -> None:
    def worker(d: int) -> int:
        return d + 1

    output: List[Optional[int]] = run_tasks(
        [1, 2, 2, 1], worker,
    )
    from pprint import pprint

    pprint(output)
    assert output == [None, 3, None, 2]


test2()

and so someone unfamiliar with the intricacies and details may be confused by the type signature being correctly updated in 2 places, but not validating.

Instead, I sort of expected it to either (a) work, or (b) have an error specific to the case of nested Optional.

$ mypy --version
mypy 0.761

$ python --version
Python 3.7.6

flags: none.

My apologies, but I did not test with mypy from Git master.

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 damit, den bereitgestellten run_tasks-Reproducer mit mypy auszuführen und die Diagnose für nested-Optional mit dem funktionierenden Fall test2 zu vergleichen. Als erledigt gilt die Aufgabe, wenn der Fehler für inkompatible Callable korrigiert oder durch eine Diagnose ersetzt wurde, die diesen Fall von nested Optional korrekt erklärt, und eine Regressionstestabdeckung für das Beispiel vorhanden 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
35/100

Neue Issues direkt in Ihr Postfach

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