目录

Python中的类型标注

类型标注

类型标注,称为 Type Hints 或者 Type annotation,由以下几个 PEP 定义:

  • PEP 3107,Python 3.0,增加了 function annotation syntax,但没有实现该语义;
  • PEP 484,Python 3.5,增加了 provisional module 以支持该特性;
  • PEP 526,Python 3.6,增加了 variable annotations syntax。

与静态语言的类型声明相比,Python 中的类型标注不会在运行时做类型检查

基本语法

function annotation

1
2
def greetingnamestr, age: int = 13 - > str
    return 'Hello'+ name + str(age)

类型标注可接受的类型有:内置类(包括标准库和第三方库中定义的类)、抽象基类、types 模块中定义的类型,用户定义的类。除此之外,还可以是 NoneAnyUnionTupleCallable 等类型。

variable annotation

全局变量声明,只声明但没有初始化的变量,在引用时会报错。

1
2
some_number: int           # variable without initial value
some_list: List[int] = []  # variable with initial value

类变量声明

1
2
3
4
class BasicStarship:
    captain: str = 'Picard'               # instance variable with default
    damage: int                           # instance variable without default
    stats: ClassVar[Dict[str, int]] = {}  # class variable

ClassVar 是 Python 3.5 引入的 typing 模块中定义的。

类型检查工具

可以使用 mypy、pytype 等工具按照类型标注对代码进行检查。

PEP563

如下代码使用了类型标注,mypy 检查没有问题,但运行时会报错 NameError: name 'Subject' is not defined

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from typing import Union
from dataclasses import dataclass

import pymysql.cursors

connection = pymysql.connect(host='localhost',
                             user='root',
                             password='',
                             db='test',
                             charset='utf8mb4')


@dataclass
class Subject:
    id: int
    cat_id: int
    title: str
    kind: int = 0

    @classmethod
    def get(cls, id: int) -> Union[Subject, None]:
        with connection.cursor() as cursor:
            cursor.execute(
                "select id, cat_id, title, kind from subject where id=%s", id)
            rs = cursor.fetchone()
        if not rs:
            return None
        return cls(*rs)

原因是类型注解部分求值太早了,那个时候类还没创建完!,详见 PEP 563。解决方案有三种:

Python 3.7 延迟求值

如果 Python 是 3.7 版本,可以在文件开头加入:

1
from __future__ import annotations

以启用延迟求值的特性。

使用字符串代替类名

将 get 方法中类名改为用字符串代替,参考 PEP484 中的讨论

1
2
def get(cls, id: int) -> Union['Subject', None]:
    ...

使用 pytype 做静态检查

pytype 还能够推断未做类型标注的 Python 代码的类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  cat foo.py
def make_greeting(user_id):
    return 'hello, user' + user_id

def print_greeting():
    print(make_greeting(0))
  pytype-single foo.py
File "foo.py", line 2, in make_greeting: unsupported operand type(s) for +: 'str' and 'int' [unsupported-operands]
  Function __add__ on str expects str
Called from (traceback):
  line 5, in print_greeting

For more details, see https://github.com/google/pytype/blob/master/docs/errors.md#unsupported-operands.

参考链接