Python中typing模块详解

Python中typing模块详解

简介

Python是一门动态语言,很多时候我们可能不清楚函数参数类型或者返回值类型,很有可能导致一些类型没有指定方法,在写完代码一段时间后回过头看代码,很可能忘记了自己写的函数需要传什么参数,返回什么类型的结果,就不得不去阅读代码的具体内容,降低了阅读的速度,typing模块可以很好的解决这个问题。

Python 运行时并不强制标注函数和变量类型。类型标注可被用于第三方工具,比如类型检查器、集成开发环境、静态检查器等。

自python3.5开始,PEP484为python引入了类型注解(type hints),typing的主要作用有:

  1. 类型检查,防止运行时出现参数、返回值类型不符。

  2. 作为开发文档附加说明,方便使用者调用时传入和返回参数类型。

  3. 模块加入不会影响程序的运行不会报正式的错误,pycharm支持typing检查错误时会出现黄色警告。

别名和NewType

1. 类型别名

要定义一个类型别名,可以将一个类型赋给别名。类型别名可用于简化复杂类型签名,在下面示例中,Vectorlist[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模块最基本的支持由 AnyTupleCallableTypeVarGeneric类型组成。

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 

Anyobject 的行为对比。与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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 143,906评论 1 305
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,698评论 1 257
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 95,242评论 0 213
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 41,257评论 0 182
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 49,076评论 1 260
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,912评论 1 178
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,509评论 2 274
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,255评论 0 168
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 29,145评论 6 235
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,613评论 0 214
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,376评论 2 216
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,733评论 1 231
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,287评论 0 32
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,196评论 2 213
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,644评论 3 211
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,651评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,059评论 0 169
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,644评论 2 233
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,789评论 2 237