-
Hello, this came up while I was doing type annotation research into descriptors. I was wondering how Pyright works with respect to determining which @overload decorated function/method is chosen for presentation via Pylance. Consider the following code, which is me copy-pasting, name-changing, and simplifying @lazy_property's __get__() from evennia in order to better understand how it works. from typing import Generic, TypeVar, overload
from typing_extensions import Self
_T = TypeVar("_T")
class Descriptor(Generic[_T]):
myname: str = "Descriptor!"
def __set_name__(self: Self, owner: type[object], name: str):
self.name = name
@overload
def __get__(self: Self, obj: object, owner: type[object]) -> _T:
...
@overload
def __get__(self: Self, obj: None, owner: type[object]) -> Self:
...
def __get__(self: Self, obj: object | None, owner: type[object]) -> Self | _T:
if obj is None:
return self
value = obj.__dict__.get(self.name)
return value
def __set__(self, obj: object, value: _T):
obj.__dict__[self.name] = value
class TestClass:
hello = Descriptor[str]()
desc = Descriptor[str]()
def __init__(self, value: str):
self.hello = value
if __name__ == "__main__":
test = TestClass("Hi!")
h1 = test.hello
h2 = TestClass.desc
test = TestClass.desc.myname When inspecting the type values of h1 and h2, both evaluate to str, which seems odd. Shouldn't h2 be Descriptor[str]? On runtime, this is indeed what it is, but the type checker will adamantly insist that h2 is a string object. (I removed a few debug print statements from this code copy-paste, which is what you can see in the image.) When you reverse the order of the two stubs decorated by @overload above, however, the correct type is given. So my question is: if None is being passed to __get__() when referencing TestClass.desc, why is the first @overload (that returns _T) chosen when the type annotation for obj is object and not None? Why does the type evaluation work correctly when the None-annotated __get__() is listed first (instead of second as in the above code)? Is this a bug or is it how Pyright works? Thanks for your time reading this! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
This is expected behavior. Overloads are evaluated in the order in which they are defined. Since |
Beta Was this translation helpful? Give feedback.
This is expected behavior. Overloads are evaluated in the order in which they are defined. Since
None
is a subtype ofobject
, you need to swap the order of the overloads so theNone
overload is tested first.