python / python/mypy

(🐞) Failure to narrow lambdas when using `Protocol` with `__call__`

Abierto
#16,797 1 comentario 2 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

bug
Lenguaje dominante
Python
Estrellas
20.6k
Forks
3.3k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

Bug Report

Mypy fails to narrow lambdas and detect errors when using a Protocol with a __call__ method, in situations where using the equivalent Callable formulation results in narrowing and error detection.

To Reproduce

In the following snippet, type narrowing is performed on the lambda function passed to the validator argument of validate10, resulting in the implementation typo being caught by Mypy:

from typing import Callable, TypeVar

InstanceT = TypeVar("InstanceT")

def validate10(instance: InstanceT, validator: Callable[[InstanceT, int], bool]) -> bool:
    return validator(instance, 10)

class MyClass:
    def validate(self, value: int) -> bool:
        return value >= 10

instance = MyClass()
validate10(instance, lambda self, value: self.validatez(value)) # Mypy error
# "MyClass" has no attribute "validatez"; maybe "validate"?

The following snippet is essentially equivalent to the above, but using Protocol in place of Callable. The type of the lambda function is no longer narrowed, and the implementation typo is not caught:

from typing Protocol, TypeVar

InstanceT = TypeVar("InstanceT")
InstanceT_contra = TypeVar("InstanceT_contra", contravariant=True)
class Validator(Protocol[InstanceT_contra]):

    def __call__(self, instance: InstanceT_contra, value: int, /) -> bool:
        ...

def validate10(instance: InstanceT, validator: Validator[InstanceT]) -> bool:
    return validator(instance, 10)

class MyClass:
    def validate(self, value: int) -> bool:
        return value >= 10

instance = MyClass()
validate10(instance, lambda self, value: self.validatez(value)) # No error

I would expect the narrowing behaviour in the two snippets to be the same (or close enough).
Please note that the issue persists even when the type variable is removed:

class MyClass:
    def validate(self, value: int) -> bool:
        return value >= 10

class Validator(Protocol):

    def __call__(self, instance: MyClass, value: int, /) -> bool:
        ...

def validate10(instance: MyClass, validator: Validator) -> bool:
    return validator(instance, 10)

instance = MyClass()
validate10(instance, lambda self, value: self.validatez(value)) # No error

Pylance/Pyright (strict) detects the typo in all cases.

Your Environment

  • Mypy version used: 1.7.1
  • Mypy command-line flags: --strict
  • Python version used: 3.12

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Empieza con las reproducciones de Callable y Protocol en torno a validate10, Validator y la lambda pasada a validator, comparando sus tipos de parámetros inferidos con --strict. Se considera terminado cuando los casos basados en Protocol acoten la lambda como el caso de Callable e informen del atributo mal escrito validatez, incluso cuando se elimine la variable de tipo.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
devtools
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Estancado
Claridad
Bastante claro
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.