python / python/mypy

Generic Callable return type inferred as first overload from overloaded function

Offen
#21,560 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

Apologies in advance if this duplicates an existing issue. I reviewed a bunch, but none seemed like an exact match. #15015 and #15737 were the closest.

https://mypy-play.net/?mypy=latest&python=3.13&gist=24cbdbfe356ffc9d12b75d5960dc2c4c

from collections.abc import Callable
from typing import Any, TypeVar, overload

T = TypeVar('T')

def call(func: Callable[..., T], *args, **kwargs) -> T:
    return func(*args, **kwargs)

@overload
def f(x: int) -> int:
    ...
@overload
def f(x: str) -> str:
    ...

def f(x):
    return x

reveal_type(call(f, 1))
reveal_type(call(f, "a"))

Expected result: I expected that mypy would either:

  • evaluate the overload for each call and reveal int for the first call, str for the second; or
  • raise an error that T can't be resolved.
  • (or maybe reveal int | str? or even Any? would prefer one of the first two though.)

Actual result: mypy silently uses the return type from the first overload, does not raise any error.

main.py:19: note: Revealed type is "int"
main.py:20: note: Revealed type is "int"
Success: no issues found in 1 source file

My actual use case is that I wanted to write:

# requires: boto3, boto3-stubs[logs]
import functools
import boto3
get_client = functools.cache(boto3.Session().client)
reveal_type(get_client('logs'))
# expected: Revealed type is "mypy_boto3_logs.client.CloudWatchLogsClient"
# actual: Revealed type is "mypy_boto3_accessanalyzer.client.AccessAnalyzerClient" if mypy-boto3-accessanalyzer is installed, "Any" otherwise

boto3.Session.client is an overloaded method. As above, mypy didn't raise any error, but just took the first overload.

The relevant declaration of functools.cache: https://github.com/python/typeshed/blob/1d3abc42077c2ea47985363a4fab387357f3aa77/stdlib/functools.pyi#L263

def cache(user_function: Callable[..., _T], /) -> _lru_cache_wrapper[_T]: ...

If the entire Callable is used (with or without ParamSpec), mypy is able to see the overloads. I don't need the other methods of _lru_cache_wrapper, so this workaround works for me:

from collections.abc import Callable
import functools
from typing import Any, TypeVar, cast

import boto3

T = TypeVar('T', bound=Callable[..., Any])

def cache(func: T) -> T:
    return cast(T, functools.cache(func))

get_client = cache(boto3.Session().client)
reveal_type(get_client('logs'))
# Revealed type is "mypy_boto3_logs.client.CloudWatchLogsClient"

Thank you for maintaining mypy!

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 verknüpften mypy-play-Reproduzierer auszuführen und den Fall Callable[..., T] mit dem whole-Callable-Workaround zu vergleichen. Lies anschließend den Pfad für die generische Callable-Inferenz im Zusammenhang mit der _T-Deklaration von functools.cache und füge dann Tests hinzu, die zeigen, dass überladene Aufrufe nicht stillschweigend die erste Überladung verwenden, sondern stattdessen eines der erwarteten Ergebnisse liefern.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
compilers, tooling
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Ruhig
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
48/100

Neue Issues direkt in Ihr Postfach

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