假设我们有以下类:
class Foo:
def __init__(self, method):
self.method = method
def __get__(self, instance, owner):
if instance is None:
return self
return self.method(instance)
class Bar:
@Foo
def do_something(self) -> int:
return 1
Bar().do_something # is 1
Bar.do_something # is Foo object
如何正确输入 hint
__get__
和 method
以便 Pylance 理解 Bar().do_something
是 do_something
的返回类型? (像标准property
)
你需要重载
__get__
方法。
我自己不使用 VSCode,但我用 MyPy 测试了下面的代码,我希望 Pyright 也能正确推断类型。
>=3.9
为了尽可能灵活,我建议使
Foo
在方面通用
from collections.abc import Callable
from typing import Generic, TypeVar, Union, overload
from typing_extensions import Concatenate, ParamSpec, Self
T = TypeVar("T") # class using the descriptor
P = ParamSpec("P") # parameter specs of the decorated method
R = TypeVar("R") # return value of the decorated method
class Foo(Generic[T, P, R]):
method: Callable[Concatenate[T, P], R]
def __init__(self, method: Callable[Concatenate[T, P], R]) -> None:
self.method = method
@overload
def __get__(self, instance: T, owner: object) -> R: ...
@overload
def __get__(self, instance: None, owner: object) -> Self: ...
def __get__(self, instance: Union[T, None], owner: object) -> Union[Self, R]:
if instance is None:
return self
return self.method(instance)
演示:
from typing import TYPE_CHECKING
class Bar:
@Foo
def do_something(self) -> int:
return 1
a = Bar().do_something
b = Bar.do_something
print(type(a), type(b)) # <class 'int'> <class '__main__.Foo'>
if TYPE_CHECKING:
reveal_locals()
在上面运行 MyPy 会给出所需的输出:
note: Revealed local types are:
note: a: builtins.int
note: b: Foo[Bar, [], builtins.int]
注意:(感谢@SUTerliakov 指出其中的一些内容)
>=3.10
,则可以直接从 Concatenate
导入
ParamSpec
和
typing
并且可以使用 |
-notation 而不是 typing.Union
。>=3.11
,您也可以直接从 Self
导入
typing
,这意味着您根本不需要 typing_extensions
。<3.9
Concatenate
、ParamSpec
和Self
,我们仍然可以根据修饰方法的返回值使Foo
泛型:
from __future__ import annotations
from collections.abc import Callable
from typing import Generic, TypeVar, Union, overload
R = TypeVar("R") # return value of the decorated method
class Foo(Generic[R]):
method: Callable[..., R]
def __init__(self, method: Callable[..., R]) -> None:
self.method = method
@overload
def __get__(self, instance: None, owner: object) -> Foo[R]: ...
@overload
def __get__(self, instance: object, owner: object) -> R: ...
def __get__(self, instance: object, owner: object) -> Union[Foo[R], R]:
if instance is None:
return self
return self.method(instance)
来自上面的相同演示脚本的 MyPy 输出:
note: Revealed local types are:
note: a: builtins.int
note: b: Foo[builtins.int]