这不是TypeVar 通常的用途。
以下函数是TypeVar 通常用于的那种函数的一个很好的例子:
def baz(obj):
return obj
这个函数可以处理任何类型的参数,所以注释这个函数的一个解决方案是使用typing.Any,像这样:
from typing import Any
def baz(obj: Any) -> Any:
return obj
但是,这不是很好。我们通常应该使用Any 作为最后的手段,因为它不会向类型检查器提供有关我们代码中变量的任何信息。如果我们过于随意地使用Any,很多潜在的错误就会被忽视,因为类型检查器基本上会放弃,而不是检查我们代码的那部分。
在这种情况下,我们可以向类型检查器提供更多信息。我们不知道输入参数的类型是什么,也不知道返回类型是什么,但我们确实知道输入类型和返回类型将是相同的,无论它们是什么是。我们可以通过使用TypeVar 来展示类型之间的这种关系——依赖于类型的关系:
from typing import TypeVar
T = TypeVar('T')
def baz(obj: T) -> T:
return obj
我们也可以在类似但更复杂的情况下使用TypeVars。考虑这个函数,它将接受任何类型的序列,并使用该序列构造一个字典:
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的元素的类型是什么,我们都可以保证返回的字典的值是相同的类型。
绑定TypeVars
绑定TypeVars 适用于当我们有一个函数,其中我们有某种类型的依赖,像上面那样,但我们想进一步缩小所涉及的类型。例如,想象以下代码:
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
在这段代码中,我们有一个与前面的例子一样的类型依赖,但还有一个额外的复杂性:如果传递给它的参数不是 - 或BreakfastFood 类的子类的实例。为了让这个函数通过类型检查器,我们需要将我们使用的TypeVar 限制为BreakfastFood 及其子类。我们可以通过使用 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 中的所有对象都具有@987654351 @ 属性。通过将T TypeVar 绑定到MyClass 的实例和子类的实例,我们告诉类型检查器obj 参数应该是MyClass 的实例或子类的实例的MyClass。 MyClass 及其子类的所有实例都具有 x 属性,因此类型检查器很高兴。万岁!
但是,在我看来,您当前的函数根本不应该使用TypeVars,因为您的函数注释中不涉及类型依赖性。如果您知道obj 参数应该是MyClass 的实例或子类的实例,并且您的注释中没有类型依赖性,那么您可以直接使用MyClass 简单地注释您的函数:
class MyClass:
x: int = 10
def foo(obj: MyClass) -> None:
print(obj.x)
foo(MyClass())
另一方面,如果obj 不需要是 MyClass 的实例或子类的实例,实际上任何具有 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 是一篇很棒的博文。