python / python/mypy

Decorated __init__ confuses callback type if decorator is defined with type alias or if decorator is imported using relative import

Ouverte
#11,293 3 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

bug topic-type-alias
Langage dominant
Python
Étoiles
20.6k
Forks
3.3k
Métriques de merge des PR
Métriques de PR en attente

Description

This one's just weird. I'm not sure if this is a dup of #1927, but I figured I'd raise it anyway.

# test_case.py
from typing import Callable, Iterable, TypeVar, Union

_T = TypeVar("_T")

def _identity(__: _T) -> _T:
    return __

# decr: Callable[[_T], _T] = _identity  # <-- this approach works
DecoratorT = Callable[[_T], _T]
decr: DecoratorT = _identity  # <-- this causes the false positives below

class Result:
    @decr
    def __init__(self):
        pass

res = Result()  # ; reveal_type(res)  # -> Any (should be test_case.Result)

def some_results(op: Callable[[], Union[Result, Iterable[Result]]]) -> Iterable[Result]:
    result_or_results = op()
    results: Iterable[Result]
    if isinstance(result_or_results, Result):
        results = (result_or_results,)  # ; reveal_type(result_or_results)  # -> (should be test_case.Result)
    else:
        results = result_or_results  # ; reveal_type(result_or_results)  # -> Union[test_case.Result, typing.Iterable[test_case.Result]] (should be typing.Iterable[test_case.Result])
    return results
% python --version ; mypy --version
Python 3.9.7
mypy 0.910
% mypy --config-file=pyproject.toml test_case.py
test_case.py:26: error: Incompatible types in assignment (expression has type "Union[Result, Iterable[Result]]", variable has type "Iterable[Result]")
Found 1 error in 1 file (checked 1 source file)

Note that if DecoratorT is defined as follows, things work again:

# …
class DecoratorT(Protocol):
    def __call__(self, __: _T) -> _T:
        ...
decr: DecoratorT = _identity  # <-- all good
# …

But if we move the decorator into its own module and use a relative import, it breaks no matter how we define the decorator type:

# test_case.py
from typing import Callable, Iterable, TypeVar, Union
# from test_case_import import decr  # <-- this works
from .test_case_import import decr  # <-- this causes the false positives below 

class Result:
    @decr
    def __init__(self):
        pass

res = Result()  # ; reveal_type(res)  # -> Any (should be test_case.Result)

def some_results(op: Callable[[], Union[Result, Iterable[Result]]]) -> Iterable[Result]:
    result_or_results = op()
    results: Iterable[Result]
    if isinstance(result_or_results, Result):
        results = (result_or_results,)  # ; reveal_type(result_or_results)  # -> (should be test_case.Result)
    else:
        results = result_or_results  # ; reveal_type(result_or_results)  # -> Union[test_case.Result, typing.Iterable[test_case.Result]] (should be typing.Iterable[test_case.Result])
    return results
# test_case_import.py
from typing import Callable, TypeVar
_T = TypeVar("_T")

def _identity(__: _T) -> _T:
    return __

decr: Callable[[_T], _T] = _identity  # <-- doesn't matter how this is defined
% mypy --config-file=pyproject.toml test_case.py test_case_import.py
test_case.py:19: error: Incompatible types in assignment (expression has type "Union[Result, Iterable[Result]]", variable has type "Iterable[Result]")
Found 1 error in 1 file (checked 2 source files)

Using the Protocol approach does not salvage the second (relative import) scenario.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par reproduire les diagnostics signalés avec mypy dans test_case.py et test_case_import.py, en comparant les cas du décorateur local et du décorateur importé par une importation relative. Suivez la façon dont mypy infère le init décoré et les types du décorateur importé. Le travail est terminé lorsque les exemples infèrent Result et Iterable[Result] conformément aux annotations, sans produire à tort une erreur d’affectation.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
devtools
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
35/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.