Mypy - 为什么 TypeVar 在没有指定边界的情况下无法工作
Mypy - why does TypeVar not work without bound specified
我正在尝试理解类型注释,我有以下代码:
from typing import TypeVar
T = TypeVar('T')
class MyClass():
x: int = 10
def foo(obj: T) -> None:
print(obj.x)
foo(MyClass())
When I run mypy,我收到以下错误:
main.py:9: error: "T" has no attribute "x"
Found 1 error in 1 file (checked 1 source file)
但是当我将 bound='MyClass'
添加到 TypeVar
时,它没有显示任何错误。
这种行为的原因是什么?我试图阅读文档,但没有找到关于将 bound
设置为默认值时到底发生了什么的任何答案。
这不是 TypeVar
通常的用途。
以下函数是 TypeVar
通常用于的函数类型的一个很好的示例:
def baz(obj):
return obj
此函数可以处理任何类型的参数,因此注释此函数的一种解决方案是使用 typing.Any
,如下所示:
from typing import Any
def baz(obj: Any) -> Any:
return obj
但这不是很好。我们通常只应将 Any
用作最后的手段,因为它不会向类型检查器提供有关代码中变量的任何信息。如果我们过于随意地使用 Any
,许多潜在的错误将在雷达下飞过,因为类型检查器基本上会放弃,而不检查我们代码的那部分。
在这种情况下,我们可以向类型检查器提供更多信息。我们不知道输入参数的类型是什么,也不知道 return 类型是什么,但我们知道 输入类型和 return 类型将是相同的,无论它们是什么 。我们可以通过使用 TypeVar
:
来显示类型之间的这种关系——一种依赖于类型的关系
from typing import TypeVar
T = TypeVar('T')
def baz(obj: T) -> T:
return obj
我们也可以在类似但更复杂的情况下使用 TypeVar
。考虑这个函数,它将接受任何类型的序列,并使用该序列构造一个字典:
def bar(some_sequence):
return {some_sequence.index(elem): elem for elem in some_sequence}
我们可以这样注释这个函数:
from typing import TypeVar, Sequence
V = TypeVar('V')
def bar(some_sequence: Sequence[V]) -> dict[int, V]:
return {some_sequence.index(elem): elem for elem in some_sequence}
无论 some_sequence
的元素的推断类型是什么,我们都可以保证 returned 的字典的值是相同的类型。
绑定TypeVar
s
Bound TypeVar
s 在我们有一个函数时很有用,我们有像上面那样的某种类型依赖性,但我们想进一步缩小涉及的类型。例如,假设以下代码:
class BreakfastFood:
pass
class Spam(BreakfastFood):
pass
class Bacon(BreakfastFood):
pass
def breakfast_selection(food):
if not isinstance(food, BreakfastFood):
raise TypeError("NO.")
# do some more stuff here
return food
在这段代码中,我们有一个类型依赖,就像前面的例子一样,但是有一个额外的复杂性:如果传递给它的参数不是一个实例,该函数将抛出一个 TypeError
的——或子class的实例——BreakfastFood
class。为了让这个函数通过类型检查器,我们需要将我们使用的 TypeVar
限制为 BreakfastFood
及其子 class 。我们可以通过使用 bound
关键字参数来做到这一点:
from typing import TypeVar
class BreakfastFood:
pass
B = TypeVar('B', bound=BreakfastFood)
class Spam(BreakfastFood):
pass
class Bacon(BreakfastFood):
pass
def breakfast_selection(food: B) -> B:
if not isinstance(food, BreakfastFood):
raise TypeError("NO.")
# do some more stuff here
return food
您的代码中发生了什么
如果您在 foo
函数中用未绑定的 TypeVar
注释 obj
参数,您是在告诉类型检查器 obj
可以是任何类型类型。但是类型检查器在这里正确地引发了一个错误:你已经告诉它 obj
可以是任何类型,但是你的函数假设 obj
有一个属性 x
,而不是所有python 中的对象具有 x
属性。通过将 T
TypeVar 绑定到 — MyClass
的实例和子 classes 的实例,我们告诉类型检查器 obj
参数应该是一个MyClass
的实例,或 MyClass
的子 class 的实例。 MyClass
及其子 class 的所有实例都具有 x
属性,因此类型检查器很高兴。万岁!
但是,在我看来,您当前的函数根本不应该使用 TypeVar
,因为函数的注释中不涉及任何类型依赖性。如果您知道 obj
参数应该是 — MyClass
的一个实例或子 class 的一个实例,并且您的注释中没有类型依赖性,那么您可以只需直接使用 MyClass
:
注释您的函数
class MyClass:
x: int = 10
def foo(obj: MyClass) -> None:
print(obj.x)
foo(MyClass())
另一方面,如果 obj
不需要是 MyClass 的实例或子class 的实例,实际上任何 class 使用 x
属性即可,然后您可以使用 typing.Protocol
来指定:
from typing import Protocol
class SupportsXAttr(Protocol):
x: int
class MyClass:
x: int = 10
def foo(obj: SupportsXAttr) -> None:
print(obj.x)
foo(MyClass())
完全解释 typing.Protocol
超出了这个已经很长的答案的范围,但是 here's 一个很棒的博客 post 就可以了。
我正在尝试理解类型注释,我有以下代码:
from typing import TypeVar
T = TypeVar('T')
class MyClass():
x: int = 10
def foo(obj: T) -> None:
print(obj.x)
foo(MyClass())
When I run mypy,我收到以下错误:
main.py:9: error: "T" has no attribute "x"
Found 1 error in 1 file (checked 1 source file)
但是当我将 bound='MyClass'
添加到 TypeVar
时,它没有显示任何错误。
这种行为的原因是什么?我试图阅读文档,但没有找到关于将 bound
设置为默认值时到底发生了什么的任何答案。
这不是 TypeVar
通常的用途。
以下函数是 TypeVar
通常用于的函数类型的一个很好的示例:
def baz(obj):
return obj
此函数可以处理任何类型的参数,因此注释此函数的一种解决方案是使用 typing.Any
,如下所示:
from typing import Any
def baz(obj: Any) -> Any:
return obj
但这不是很好。我们通常只应将 Any
用作最后的手段,因为它不会向类型检查器提供有关代码中变量的任何信息。如果我们过于随意地使用 Any
,许多潜在的错误将在雷达下飞过,因为类型检查器基本上会放弃,而不检查我们代码的那部分。
在这种情况下,我们可以向类型检查器提供更多信息。我们不知道输入参数的类型是什么,也不知道 return 类型是什么,但我们知道 输入类型和 return 类型将是相同的,无论它们是什么 。我们可以通过使用 TypeVar
:
from typing import TypeVar
T = TypeVar('T')
def baz(obj: T) -> T:
return obj
我们也可以在类似但更复杂的情况下使用 TypeVar
。考虑这个函数,它将接受任何类型的序列,并使用该序列构造一个字典:
def bar(some_sequence):
return {some_sequence.index(elem): elem for elem in some_sequence}
我们可以这样注释这个函数:
from typing import TypeVar, Sequence
V = TypeVar('V')
def bar(some_sequence: Sequence[V]) -> dict[int, V]:
return {some_sequence.index(elem): elem for elem in some_sequence}
无论 some_sequence
的元素的推断类型是什么,我们都可以保证 returned 的字典的值是相同的类型。
绑定TypeVar
s
Bound TypeVar
s 在我们有一个函数时很有用,我们有像上面那样的某种类型依赖性,但我们想进一步缩小涉及的类型。例如,假设以下代码:
class BreakfastFood:
pass
class Spam(BreakfastFood):
pass
class Bacon(BreakfastFood):
pass
def breakfast_selection(food):
if not isinstance(food, BreakfastFood):
raise TypeError("NO.")
# do some more stuff here
return food
在这段代码中,我们有一个类型依赖,就像前面的例子一样,但是有一个额外的复杂性:如果传递给它的参数不是一个实例,该函数将抛出一个 TypeError
的——或子class的实例——BreakfastFood
class。为了让这个函数通过类型检查器,我们需要将我们使用的 TypeVar
限制为 BreakfastFood
及其子 class 。我们可以通过使用 bound
关键字参数来做到这一点:
from typing import TypeVar
class BreakfastFood:
pass
B = TypeVar('B', bound=BreakfastFood)
class Spam(BreakfastFood):
pass
class Bacon(BreakfastFood):
pass
def breakfast_selection(food: B) -> B:
if not isinstance(food, BreakfastFood):
raise TypeError("NO.")
# do some more stuff here
return food
您的代码中发生了什么
如果您在 foo
函数中用未绑定的 TypeVar
注释 obj
参数,您是在告诉类型检查器 obj
可以是任何类型类型。但是类型检查器在这里正确地引发了一个错误:你已经告诉它 obj
可以是任何类型,但是你的函数假设 obj
有一个属性 x
,而不是所有python 中的对象具有 x
属性。通过将 T
TypeVar 绑定到 — MyClass
的实例和子 classes 的实例,我们告诉类型检查器 obj
参数应该是一个MyClass
的实例,或 MyClass
的子 class 的实例。 MyClass
及其子 class 的所有实例都具有 x
属性,因此类型检查器很高兴。万岁!
但是,在我看来,您当前的函数根本不应该使用 TypeVar
,因为函数的注释中不涉及任何类型依赖性。如果您知道 obj
参数应该是 — MyClass
的一个实例或子 class 的一个实例,并且您的注释中没有类型依赖性,那么您可以只需直接使用 MyClass
:
class MyClass:
x: int = 10
def foo(obj: MyClass) -> None:
print(obj.x)
foo(MyClass())
另一方面,如果 obj
不需要是 MyClass 的实例或子class 的实例,实际上任何 class 使用 x
属性即可,然后您可以使用 typing.Protocol
来指定:
from typing import Protocol
class SupportsXAttr(Protocol):
x: int
class MyClass:
x: int = 10
def foo(obj: SupportsXAttr) -> None:
print(obj.x)
foo(MyClass())
完全解释 typing.Protocol
超出了这个已经很长的答案的范围,但是 here's 一个很棒的博客 post 就可以了。