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