
Magdalena Tomczyk的插图
在本文的第一部分中,我描述了使用类型注释的基础。 但是,没有考虑几个要点。 首先,泛型是一种重要的机制,其次,有时在运行时查找有关预期类型的信息可能很有用。 但是我想从简单的事情开始
提前公告
通常,您不能在创建类型之前使用它。 例如,以下代码甚至无法启动:
class LinkedList: data: Any next: LinkedList
要解决此问题,可以使用字符串文字。 在这种情况下,注释将被延迟计算。
class LinkedList: data: Any next: 'LinkedList'
您还可以从其他模块访问类(当然,如果导入了模块): some_variable: 'somemodule.SomeClass'
备注一般来说,任何可计算的表达式都可以用作注释。 但是,建议将它们保持尽可能简单,以便静态分析实用程序可以使用它们。 特别是,他们很可能不会理解动态可计算类型。 在此处了解有关限制的更多信息: PEP 484-类型提示#可接受的类型提示
例如,以下代码将运行,甚至注释将在运行时可用,但是mypy会在其上引发错误
def get_next_type(arg=None): if arg: return LinkedList else: return Any class LinkedList: data: Any next: 'get_next_type()'
UPD :在Python 4.0中,计划启用延迟类型注释计算( PEP 563 ),这将删除带有字符串文字的此技术。 使用Python 3.7,您可以使用from __future__ import annotations
构造启用新行为
函数和被调用对象
对于需要传输函数或调用另一个对象(例如,作为回调)的情况,您需要使用Callable批注[[ArgType1,ArgType2,...],ReturnType]
举个例子
def help() -> None: print("This is help string") def render_hundreds(num: int) -> str: return str(num // 100) def app(helper: Callable[[], None], renderer: Callable[[int], str]): helper() num = 12345 print(renderer(num)) app(help, render_hundreds) app(help, help)
只允许指定函数的返回类型而不指定其参数。 在这种情况下,使用省略号: Callable[..., ReturnType]
。 请注意,省略号周围没有方括号。
目前,无法用可变数量的某种类型的参数来描述函数签名或指定命名参数。
通用类型
有时有必要保存有关类型的信息,而不必严格固定。 例如,如果您编写一个存储相同数据的容器。 或返回与参数之一相同类型的数据的函数。
我们之前看到的诸如List或Callable之类的类型仅使用通用机制。 但是除了标准类型,您还可以创建自己的泛型类型。 为此,首先,给TypeVar一个变量,该变量将是泛型的一个属性,其次,直接声明一个泛型类型:
T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" def __init__(self, data: T): self.data = data head_int: LinkedList[int] = LinkedList(1) head_int.next = LinkedList(2) head_int.next = 2
如您所见,对于泛型类型,参数类型的自动推断起作用。
如果需要,泛型可以具有任意数量的参数: Generic[T1, T2, T3]
。
另外,在定义TypeVar时,您可以限制有效类型:
T2 = TypeVar("T2", int, float) class SomethingNumeric(Generic[T2]): pass x = SomethingNumeric[str]()
演员表
有时,分析器静态分析器无法正确确定变量的类型,在这种情况下,可以使用强制转换功能。 它的唯一任务是向分析器显示表达式是特定类型的。 例如:
from typing import List, cast def find_first_str(a: List[object]) -> str: index = next(i for i, x in enumerate(a) if isinstance(x, str)) return cast(str, a[index])
它对于装饰器也可能有用:
MyCallable = TypeVar("MyCallable", bound=Callable) def logged(func: MyCallable) -> MyCallable: @wraps(func) def wrapper(*args, **kwargs): print(func.__name__, args, kwargs) return func(*args, **kwargs) return cast(MyCallable, wrapper) @logged def mysum(a: int, b: int) -> int: return a + b mysum(a=1)
使用运行时注释
尽管解释器不单独使用注释,但是在程序运行时,注释可用于您的代码。 为此,提供了一个__annotations__
对象属性,该属性包含带有指示的注释的字典。 对于函数,这些是参数注释和返回类型,对于对象是字段注释,对于全局范围,变量及其注释。
def render_int(num: int) -> str: return str(num) print(render_int.annotations)
还可以使用__annotations__
它返回传递给它的对象的注释,在许多情况下,它与__annotations__
的内容匹配,但有区别:它还添加了父对象的注释(以__mro__
的相反顺序),还允许以字符串形式指定类型的初步声明。
T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" print(LinkedList.__annotations__)
对于泛型类型,可以通过__origin__
和__args__
获得有关类型本身及其参数的信息,但这不是标准的一部分,并且行为在3.6和3.7版本之间已更改