Python类型注释简介。 延续性


Magdalena Tomczyk的插图


在本文的第一部分中,我描述了使用类型注释的基础。 但是,没有考虑几个要点。 首先,泛型是一种重要的机制,其次,有时在运行时查找有关预期类型的​​信息可能很有用。 但是我想从简单的事情开始


提前公告


通常,您不能在创建类型之前使用它。 例如,以下代码甚至无法启动:


class LinkedList: data: Any next: LinkedList # NameError: name 'LinkedList' is not defined 

要解决此问题,可以使用字符串文字。 在这种情况下,注释将被延迟计算。


 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()' # error: invalid type comment or annotation 

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) # error: Argument 2 to "app" has incompatible type "Callable[[], None]"; expected "Callable[[int], str]" 

只允许指定函数的返回类型而不指定其参数。 在这种情况下,使用省略号: 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 # error: Incompatible types in assignment (expression has type "int", variable has type "LinkedList[int]") head_int.data += 1 head_int.data.replace("0", "1") # error: "int" has no attribute "replace" head_str: LinkedList[str] = LinkedList("1") head_str.data.replace("0", "1") head_str = LinkedList[str](1) # error: Argument 1 to "LinkedList" has incompatible type "int"; expected "str" 

如您所见,对于泛型类型,参数类型的自动推断起作用。


如果需要,泛型可以具有任意数量的参数: Generic[T1, T2, T3]


另外,在定义TypeVar时,您可以限制有效类型:


 T2 = TypeVar("T2", int, float) class SomethingNumeric(Generic[T2]): pass x = SomethingNumeric[str]() # error: Value of type variable "T2" of "SomethingNumeric" cannot be "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) # error: Missing positional argument "b" in call to "mysum" 

使用运行时注释


尽管解释器不单独使用注释,但是在程序运行时,注释可用于您的代码。 为此,提供了一个__annotations__对象属性,该属性包含带有指示的注释的字典。 对于函数,这些是参数注释和返回类型,对于对象是字段注释,对于全局范围,变量及其注释。


 def render_int(num: int) -> str: return str(num) print(render_int.annotations) # {'num': <class 'int'>, 'return': <class 'str'>} 

还可以使用__annotations__它返回传递给它的对象的注释,在许多情况下,它与__annotations__的内容匹配,但有区别:它还添加了父对象的注释(以__mro__的相反顺序),还允许以字符串形式指定类型的初步声明。


 T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" print(LinkedList.__annotations__) # {'data': ~T, 'next': 'LinkedList[T]'} print(get_type_hints(LinkedList)) # {'data': ~T, 'next': __main__.LinkedList[~T]} 

对于泛型类型,可以通过__origin____args__获得有关类型本身及其参数的信息,但这不是标准的一部分,并且行为在3.6和3.7版本之间已更改

Source: https://habr.com/ru/post/zh-CN435988/


All Articles