Python中typing模块详解
简介
Python是一门动态语言,很多时候我们可能不清楚函数参数类型或者返回值类型,很有可能导致一些类型没有指定方法,在写完代码一段时间后回过头看代码,很可能忘记了自己写的函数需要传什么参数,返回什么类型的结果,就不得不去阅读代码的具体内容,降低了阅读的速度,typing模块可以很好的解决这个问题。
Python 运行时并不强制标注函数和变量类型。类型标注可被用于第三方工具,比如类型检查器、集成开发环境、静态检查器等。
自python3.5开始,PEP484为python引入了类型注解(type hints),typing的主要作用有:
类型检查,防止运行时出现参数、返回值类型不符。
作为开发文档附加说明,方便使用者调用时传入和返回参数类型。
模块加入不会影响程序的运行不会报正式的错误,pycharm支持typing检查错误时会出现黄色警告。
别名和NewType
1. 类型别名
要定义一个类型别名,可以将一个类型赋给别名。类型别名可用于简化复杂类型签名,在下面示例中,Vector
和 list[float]
将被视为可互换的同义词:
Vector = list[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
# typechecks; a list of floats qualifies as a Vector.
new_vector = scale(2.0, [1.0, -4.2, 5.4])
请注意,
None
作为类型提示是一种特殊情况,并且由type(None)
取代,这是因为None
是一个存在于解释器中的单例对象。
2. NewType
使用 NewType
辅助函数创建不同的类型,静态类型检查器会将新类型视为它是原始类型的子类。
from typing import NewType
UserId = NewType('UserId', int)
def get_user_name(user_id: UserId) -> str:
...
# typechecks
user_a = get_user_name(UserId(42351))
# does not typecheck; an int is not a UserId
user_b = get_user_name(-1)
仍然可以对 UserId
类型的变量执行所有的 int
支持的操作,但结果将始终为 int
类型。这可以让你在需要 int
的地方传入 UserId
,但会阻止你以无效的方式无意中创建 UserId
:
# 'output' is of type 'int', not 'UserId'
output = UserId(23413) + UserId(54341)
需要注意,这些检查仅通过静态类型检查程序来强制。
NewType
返回的是一个函数该函数立即返回传递它的任意值这就意味着UserId(1234)
并不会创建一个新的类或引入任何超出常规函数调用的开销。
因此,运行过程中same_value is Newtype("TypeName", Base)(same_value)
始终为True。
但是,可以基于NewType
创建NewType
。
使用类型别名声明两种类型彼此 等效 。
Alias = Original
将使静态类型检查对待所有情况下Alias
完全等同于Original
。当您想简化复杂类型签名时,这很有用。
相反,NewType
声明一种类型是另一种类型的子类型。Derived = NewType('Derived', Original)
将使静态类型检查器将Derived
当作Original
的 子类 ,这意味着Original
类型的值不能用于Derived
类型的值需要的地方。当您想以最小的运行时间成本防止逻辑错误时,这非常有用。
常用类型
typing模块最基本的支持由 Any
,Tuple
,Callable
,TypeVar
和 Generic
类型组成。
1. 泛型集合类型
class typing.List
(list, MutableSequence[T])
list的泛型版本。用于注释返回类型。要注释参数,最好使用抽象集合类型,如Sequence或Iterable。示例:
T = TypeVar('T', int, float)
def vec2(x: T, y: T) -> List[T]:
return [x, y]
def keep_positives(vector: Sequence[T]) -> List[T]:
return [item for item in vector if item > 0]
class typing.Dict
(dict, MutableMapping[KT, VT])
dict 的泛型版本。对标注返回类型比较有用。如果要标注参数的话,使用如 Mapping 的抽象容器类型是更好的选择。示例:
def count_words(text: str) -> Dict[str, int]:
...
类似的类型还有
class typing.Set
(set, MutableSet[T])
2. 抽象基类
class typing.Iterable
(Generic[T_co])
要注释函数参数中的迭代类型时,推荐使用的抽象集合类型。
class typing.Sequence
(Reversible[T_co], Collection[T_co])
要注释函数参数中的序列例如列表类型时,推荐使用的抽象集合类型。
class typing.Mapping
(Sized, Collection[KT], Generic[VT_co])
要注释函数参数中的Key-Value类型时,推荐使用的抽象集合类型。
3. 泛型
class typing.TypeVar
类型变量。
需要注意的是 TypeVar
不是一个类使用 isinstance(x, T)
会在运行时抛出 TypeError
异常。一般地说, isinstance()
和 issubclass()
不应该和类型变量一起使用。示例:
T = TypeVar('T') # Can be anything
A = TypeVar('A', str, bytes) # Must be str or bytes
def repeat(x: T, n: int) -> Sequence[T]:
"""Return a list containing n references to x."""
return [x]*n
def longest(x: A, y: A) -> A:
"""Return the longest of two strings."""
return x if len(x) >= len(y) else y
typing.AnyStr
AnyStr是一个字符串和字节类型的特殊类型变量AnyStr = TypeVar('AnyStr', str, bytes)
,它用于可以接受任何类型的字符串而不允许不同类型的字符串混合的函数。
def concat(a: AnyStr, b: AnyStr) -> AnyStr:
return a + b
concat(u"foo", u"bar") # Ok, output has type 'unicode'
concat(b"foo", b"bar") # Ok, output has type 'bytes'
concat(u"foo", b"bar") # Error, cannot mix unicode and bytes
class typing.Generic
泛型的抽象基类型,泛型类型通常通过继承具有一个或多个类型变量的该类的实例来声明。
泛型类型可以有任意数量的类型变量,并且类型变量可能会受到限制。
每个参数的类型变量必须是不同的。
X = TypeVar('X')
Y = TypeVar('Y')
class Mapping(Generic[KT, VT]):
def __getitem__(self, key: KT) -> VT: ...
def lookup_name(mapping: Mapping[X, Y], key: X, default: Y) -> Y:
try:
return mapping[key]
except KeyError:
return default
- 可以对
Generic
使用多重继承。
from collections.abc import Sized
from typing import TypeVar, Generic
T = TypeVar('T')
class LinkedList(Sized, Generic[T]): ...
- 从泛型类继承时,某些类型变量可能是固定的。
from collections.abc import Mapping
from typing import TypeVar
T = TypeVar('T')
class MyDict(Mapping[str, T]): ...
4. 特殊类型
typing.Any
特殊类型,表明类型没有任何限制。
每一个类型都对
Any
兼容。Any
对每一个类型都兼容。
Any
是一种特殊的类型。静态类型检查器将所有类型视为与Any
兼容,反之亦然, Any
也与所有类型相兼容。
这意味着可对类型为 Any
的值执行任何操作或者方法调用并将其赋值给任意变量。如下所示,将 Any
类型的值赋值给另一个更具体的类型时,Python不会执行类型检查。例如,当把 a
赋值给 s
时,即使 s
被声明为 str
类型,在运行时接收到的是 int
值,静态类型检查器也不会报错
from typing import Any
a = None # type: Any
a = [] # OK
a = 2 # OK
s = '' # type: str
s = a # OK
def foo(item: Any) -> int:
# Typechecks; 'item' could be any type,
# and that type might have a 'bar' method
item.bar()
...
所有返回值无类型或形参无类型的函数将隐式地默认使用Any
类型,如下所示2种写法等效。
def legacy_parser(text):
...
return data
# A static type checker will treat the above
# as having the same signature as:
def legacy_parser(text: Any) -> Any:
...
return data
Any
和 object
的行为对比。与Any
相似,所有的类型都是object
的子类型。然而不同于 Any
,反之并不成立:object
不是 其他所有类型的子类型。
这意味着当一个值的类型是object
的时候,类型检查器会拒绝对它的几乎所有的操作。把它赋值给一个指定了类型的变量(或者当作返回值)是一个类型错误。比如说,下述代码hash_a
会被IDE标注不能从object
找到magic
的引用错误,而hash_b则不会:
def hash_a(item: object) -> int:
# Fails; an object does not have a 'magic' method.
item.magic()
def hash_b(item: Any) -> int:
# Typechecks
item.magic()
# Typechecks, since ints and strs are subclasses of objecthash_a(42)
hash_a("foo")
# Typechecks, since Any is compatible with all typeshash_b(42)
hash_b("foo")
typing.NoReturn
标记一个函数没有返回值的特殊类型。
from typing import NoReturn
def stop() -> NoReturn:
raise RuntimeError
5. 特殊形式
class typing.Type
(Generic[CT_co])
一个注解为 C
的变量可以接受一个类型为 C
的值。相对地,一个注解为 Type[C]
的变量可以接受本身为类的值 。 更精确地说它接受 C
的 类对象 ,例如:
a = 3 # Has type 'int'
b = int # Has type 'Type[int]'
c = type(a) # Also has type 'Type[int]'
注意Type[C]
是协变的:
class User: ...
class BasicUser(User): ...
class ProUser(User): ...
class TeamUser(User): ...
# Accepts User, BasicUser, ProUser, TeamUser, ...
def make_new_user(user_class: Type[User]) -> User:
# ...
return user_class()
typing.Tuple
元组类型,Tuple[X, Y]
标注了一个二元组类型,其第一个元素的类型为 X
且第二个元素的类型为Y
。空元组的类型可写作 Tuple[()]
为表达一个同类型元素的变长元组,使用省略号字面量,如Tuple[int, ...]
。单独的一个 Tuple
等价于 Tuple[Any, ...]
,进而等价于tuple
。
示例: Tuple[int, float, str]
表示一个由整数、浮点数和字符串组成的三元组。
typing.Union
联合类型;Union[X, Y]
意味着:要么是 X
,要么就是 Y
。定义一个联合类型,需要注意的有:
参数必须是类型,而且必须至少有一个参数。
能继承或者实例化一个联合类型。
Union[X, Y]
不能写成Union[X][Y]
。可以使用
Optional[X]
作为Union[X, None]
的缩写- 联合类型的联合类型会被展开打平,比如
Union[Union[int, str], float] == Union[int, str, float]
- 仅有一个参数的联合类型会坍缩成参数自身,比如:
Union[int] == int # The constructor actually returns int
- 多余的参数会被跳过,比如:
Union[int, str, int] == Union[int, str]
- 在比较联合类型的时候,参数顺序会被忽略,比如:
Union[int, str] == Union[str, int]
typing.Optional
可选类型。Optional[X]
等价于Union[X, None]
。
def sqrt(x: Union[int, float])->Optional[float]:
if x >= 0:
return math.sqrt(x)
typing.Callable
可调用类型;Callable[[int], str]
是一个函数,接受一个 int
参数,返回一个str
。下标值的语法必须恰为两个值:参数列表和返回类型。参数列表必须是一个类型和省略号组成的列表;返回值必须是单一一个类型。
不存在语法来表示可选的或关键词参数,这类函数类型罕见用于回调函数。Callable[..., ReturnType]
(使用字面省略号)能被用于提示一个可调用对象,接受任意数量的参数并且返回 ReturnType
。单独的 Callable
等价于Callable[..., Any]
,并且进而等价于 collections.abc.Callable
。