Description
openedon Jan 19, 2024
Because of the circumstances under which __set_name__
is invoked, it should be possible to perform type inference on its owner
argument, in a way which could be used to statically typecheck other components of a descriptor.
As a brief motivating example, below is a sketch of descriptor for a validated mutable attribute of integer type:
from __future__ import annotations
from typing import Any, Generic, Protocol, Self, Type, TypeVar, cast, overload
InstanceT = TypeVar("InstanceT")
InstanceT_contra = TypeVar("InstanceT_contra", contravariant=True)
class Validator(Protocol[InstanceT_contra]):
def __call__(self, instance: InstanceT_contra, value: int, /) -> bool:
...
class IntAttr(Generic[InstanceT]):
__validator: Validator[InstanceT]
__name: str
def __init__(self, validator: Validator[InstanceT]) -> None:
self.__validator = validator
def __set_name__(self, owner: Type[InstanceT], name: str) -> None:
self.__name = name
@overload
def __get__(self, instance: None, _: Type[Any]) -> Self:
...
@overload
def __get__(self, instance: InstanceT, _: Type[Any]) -> int:
...
def __get__(self, instance: InstanceT|None, _: Type[Any]) -> int | Self:
if instance is None:
return self
return cast(int, getattr(instance, f"__{self.__name}"))
def __set__(self, instance: InstanceT, value: int) -> None:
if not self.__validator(instance, value):
raise ValueError()
setattr(instance, f"__{self.__name}", value)
class C:
x = IntAttr(lambda self, value: self.validate(value)) # Pylance error
def validate(self, value: int) -> bool:
return value >= 10
In Pylance with strict typechecking rules, the definition of the x
descriptor raises the following errors:
reportUnknownMemberType
Type of "validate" is unknown
reportUnknownLambdaType
Return type of lambda is unknown
reportGeneralTypeIssues
Cannot access member "validate" for type "object*"
Member "validate" is unknown
Explicitly providing a hint for the InstanceT
type removes the errors:
class C:
x = IntAttr["C"](lambda self, value: self.validate(value)) # Errors
def validate(self, value: int) -> bool:
return value >= 10
Performing inference on the owner
argument to IntAttr.__set_name__
in the context of class C
would ideally result in C
being inferred as a value for InstanceT
, allowing for static typecheking of the validator
lambda function without the need for an explicit type hint.
Related issues for Mypy:
Sister issue for Mypy: python/mypy#16796