英文:
How to type hint python magic __get__ method
问题
如何正确地为__get__
和method
进行类型提示,以使Pylance理解Bar().do_something
的返回类型与do_something
的返回类型相同?(就像标准的property
一样)
from typing import Type, TypeVar
T = TypeVar('T')
class Foo:
def __init__(self, method: Type[T]) -> None:
self.method = method
def __get__(self, instance, owner) -> Type[T]:
if instance is None:
return self
return self.method(instance)
class Bar:
@Foo
def do_something(self) -> int:
return 1
英文:
Suppose we have the following classes:
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
How to type hint __get__
and method
correctly so that Pylance understands Bar().do_something
is of the return type of do_something
? (like a standard property
)
答案1
得分: 2
我自己不使用VSCode,但我使用MyPy测试了下面的代码,并且我希望Pyright也能正确地推断类型。
Python >=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") # 使用描述符的类
P = ParamSpec("P") # 装饰的方法的参数规范
R = TypeVar("R") # 装饰的方法的返回值
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指出的一些内容)
- 如果你使用Python
>=3.10
,你可以直接从typing
中导入Concatenate
和ParamSpec
,并且可以使用|
符号代替typing.Union
。 - 如果你使用Python
>=3.11
,你也可以直接从typing
中导入Self
,这意味着你不需要typing_extensions
。
英文:
You'll need to overload the __get__
method.
I do not use VSCode myself, but I tested the code below with MyPy and I would expect Pyright to infer the types correctly as well.
Python >=3.9
To make this as flexible as possible, I would suggest making Foo
generic in terms of
- the class using the descriptor/decorator,
- the parameter specification of the decorated method, and
- the return type of the decorated method.
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)
Demo:
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()
Running MyPy over this gives the desired output:
note: Revealed local types are:
note: a: builtins.int
note: b: Foo[Bar, [], builtins.int]
NOTE: (thanks to @SUTerliakov for pointing some of this out)
- If you are on Python
>=3.10
, you can importConcatenate
andParamSpec
directly fromtyping
and you can use the|
-notation instead oftyping.Union
. - If you are on Python
>=3.11
you can importSelf
directly fromtyping
as well, meaning you won't needtyping_extensions
at all.
Python <3.9
Without Concatenate
, ParamSpec
and Self
we can still make Foo
generic in terms of the return value of the decorated method:
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 output for the same demo script from above:
note: Revealed local types are:
note: a: builtins.int
note: b: Foo[builtins.int]
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论