Skip to content

about

如果你能访问youtube且英文能力还行的话,可以参考Guido关于type hints的演讲:https://www.youtube.com/watch?v=2wDvzy6Hgxg

参考:

Python3.5PEP484开始,引入了类型注解(type hints)机制。

主要作用:

  1. 类型检查,防止运行时出现参数和返回值类型、变量类型不符合。
  2. 三方ide的在类型检查、代码提示等功能更加方便。
  3. 作为开发文档附加说明,方便使用者调用时传入和返回参数类型。

总之,我们日常开发中仍然可以不用考虑这个类型注解,一切照旧。

但现在有了这个类型注解,我们可以更好的规范代码,比如加上类型注解之后,我们能一眼就能看懂,某个函数接受参数的类型,某些变量的类型是什么,有没有返回值,返回值的类型等。

python
user: str = ''

def foo(name: str, age: int) -> str:
    return name

比如上面的foo函数,期望传递的name参数是str类型,age参数传递的整型,而返回值则是str类型。这样的话,开发人员看到这段代码就能很好的使用这个函数了。

而且加上类型注解,你的ide会更贴心

1832669742976991232.png

但是,你愣是不按照类型注解期望的方式传递参数,当然可以,代码照常运行......至于报错或者出bug了,就需要我们自己排除了。

为啥要学习这个类型注解,因为现在有些框架比如fastapi结合pydantic就能实现加强类型注解的作用,不按照规定传参,就给你错误提示,再加上其他模块提供的调试功能,大大提高了开发和调试效率。

所以,我们要对类型注解这块,要学习,要了解。

基础的类型注解

常用的基础的类型注解有下面这些,且无需导入任何模块,就可以直接使用的。

python
a: int = 1
b: str = ''
c: float = 3.14
d: list = []
e: dict = {}
f: set = set()
g: tuple = ()

当然,内置模块typing中,罗列了几乎我们开发中常见的那些类型,比如下面这几个ListDict就等价于内置的listdict,但支持一些细节实现,我们下面会讲。

python
from typing import List, Dict, Tuple, Set

a: int = 1
b: str = ''
c: float = 3.14
# d: list = []
# e: dict = {}
# f: set = set()
# g: tuple = ()

d: List = []
e: Dict = {}
f: Set = set()
g: Tuple = ()

当然,typing模块无需下载,但是使用之前必须先导入。

概念:泛型

拿上面的Listlist来举例,我们定义了变量d: list = [],且它的类型是列表,但问题来了,列表中可以存放各种类型的数据,那你这个变量d指向的列表中,到底存放什么样的类型?你总要说一下吧,所以,泛型出来了,也就是List,可以用这个Listlist的基础上,对列表中的元素进一步进行扩展说明。所以,我们可以将List称为是list的泛型。具体用法下文见。

类型注解的使用规范

类型注解在 PEP 8 中,具体的格式是这样规定的:

  • 在声明变量类型时,变量后方紧跟一个冒号,冒号后面跟一个空格,再跟上变量的类型,再跟上一个空格,空格后面再跟上等号,等号后面再来一个空格,最后就是其值,具体就是这样的a: int = 1
  • 在声明方法返回值的时候,箭头左边是方法定义,箭头右边是返回值的类型,箭头左右两边都要留有空格,具体用法def foo(name: str, age: int) -> str:

省略号...

省略号...在类型注解时也会用到,它在这里通常用来表示同类型的可变长度的多个元素的类型。

List、Tuple、Sequence

List、Tuple

python
from typing import List, Tuple, Sequence

a: List[int] = [1, 2, 3]
b: List[str] = ['a', 'b', 'c']
c: List[List[int]] = [[1, 2], [3, 4]]

# 注意,元组的话,因为一经定义无法更改,所以,类型注解时,也要写多个,当然,要是觉得不太方方便,
# 可以用Sequence代替,或者省略号 ... 语法
d: Tuple[int, int, int] = (1, 2, 3)
e: Tuple[str, str, str] = ('a', 'b', 'c')
f: Tuple[List[int, int], List[int, int]] = ([1, 2], [3, 4])
g: Tuple[Tuple[int, int], Tuple[int, int]] = ((1, 2), (3, 4))
h: Sequence[Sequence[int], Sequence[int]] = ((1, 2), (3, 4))

# 省略号... 语法示例
# 通常写法
t1: Tuple[int, int, int] = (1, 2, 3)
print(t1)  # (1, 2, 3)

# 使用省略号 ... 语法,表示元组中的其余元素的类型也都是int类型
t2: Tuple[int, ...] = (1, 2, 3)
print(t2)  # (1, 2, 3)

# 也可以用于元组打包
t3: Tuple[int, ...] = 1, 2, 3
print(t3)  # (1, 2, 3)

Sequence

Sequencecollections.abc.Sequence 的泛型,在某些情况下,我们可能并不需要严格区分一个变量或参数到底是列表 list 类型还是元组 tuple 类型,我们可以使用一个更为泛化的类型,叫做 Sequence,其用法类似于 List,如:

python
from typing import Sequence

a: Sequence[str] = []
b: Sequence[int] = (1, 2, 3)


def foo(a: Sequence[str]) -> None:
    print(a)


foo(['a', 'b', 'c'])
foo(('a', 'b', 'c'))

Dict、Mapping

Dict、字典,是 dict 的泛型;Mapping,映射,是collections.abc.Mapping的泛型。根据官方文档,Dict 推荐用于注解返回类型,Mapping 推荐用于注解参数。它们的使用方法都是一样的,其后跟一个中括号,中括号内分别声明键名、键值的类型。

python
from typing import Dict, Mapping


def bar(m: Mapping[str, str]) -> Dict[str, str]:
    print(m)
    return {'user': m['user']}

bar({'user': "张开"})

上例将 Dict 用作了返回值类型注解,将 Mapping 用作了参数类型注解。

Set、AbstractSet

Set、集合,是 set 的泛型;AbstractSet、是collections.abc.Set的泛型。根据官方文档,Set 推荐用于注解返回类型,AbstractSet用于注解参数。它们的使用方法都是一样的,其后跟一个中括号,里面声明集合中元素的类型。

python
from typing import Set, AbstractSet


def bar(m: AbstractSet[str]) -> Set[str]:
    print(m)
    return set(m)


bar({'a', 'b'})

上例将 Set 用作了返回值类型注解,将 AbstractSet 用作了参数类型注解。

NoReturn

当一个方法没有返回结果时,为了注解它的返回类型,我们可以将其注解为 NoReturn。

python
from typing import NoReturn


def foo() -> NoReturn:
    pass

# 但我更习惯用None
def bar() -> None:
    pass

Any

https://docs.python.org/zh-cn/3/library/typing.html?highlight=typing#the-any-type

Any,是一种特殊的类型,它可以代表所有类型,静态类型检查器的所有类型都与 Any 类型兼容,所有的无参数类型注解和返回类型注解的都会默认使用 Any 类型。

这个用的也比较多,比如字典的value可以是任意类型,所以再使用类型注解时,我们拿不准value的类型时,就可以用Any。

python
from typing import Any, List, Dict


def bar(a: List[Any], d: Dict[str, Any]) -> None:
    print(a, d)


bar([1, 2, 3], {"a": 1, 'user': '张开'})

注意,Any 类型的值赋给更精确的类型时,不执行类型检查。例如,把 a 赋给 s,在运行时,即便 s 已声明为 str 类型,但接收 int 值时,静态类型检查器也不会报错。

Callable

Callable,可调用类型,它通常用来注解一个方法。

python
from typing import Callable


def foo(a: int, b: str):
    print(a, b)


def bar(func: Callable, a: int, b: str) -> None:
    print(func, a, b)
    func(a, b)


bar(foo, 1, 'a')

联合类型&&联合类型表达式

https://docs.python.org/zh-cn/3/library/typing.html#typing.Union

Union,联合类型,Union[X, Y] 代表要么是 X 类型,要么是 Y 类型。

python
import json
from decimal import Decimal
from datetime import date, datetime

from typing import Union, Any


class CustomJsonEncoder(json.JSONEncoder):
    # def default(self, field: Union[date, datetime, Decimal, Any]):
    # 在 3.10 版更改: 联合类型现在可以写成 X | Y
    def default(self, field: date | datetime | Decimal | Any):
        if isinstance(field, date):
            return field.strftime('%Y-%m-%d')
        elif isinstance(field, datetime):
            return field.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(field, Decimal):
            return float(field)
        # 如果有更多json无法序列化的类型,就继续elif手动处理
        else:  # 其他json能序列化的就调用父类的default方法就完了
            return json.JSONEncoder.default(self, field)


data = {'date': date(2023, 5, 5), 'datetime': datetime.now(), 'decimal': Decimal(3.2), 'user': '张开'}
print(json.dumps(data, cls=CustomJsonEncoder, ensure_ascii=False))

默认值

对于注解类型的应用中,也可以设置默认值:

python
def foo(x: str = '张开', y: int = 18) -> None:
    print(x, y)


# 因为有了默认值,可以不传,当然传,肯定就用你传的
foo()
foo('李开', 29)

Optional

Optional,意思是说这个参数可以为空或已经声明的类型,即 Optional[X] 等价于 Union[X, None]。 但值得注意的是,这个并不等价于可选参数,当它作为参数类型注解的时候,不代表这个参数可以不传递了,而是说这个参数可以传为 None。

python
from typing import Union, Any, Optional


def foo(x: Optional[str], y: Optional[str] = None) -> None:
    print(x, y)


# 对于形参x来说,Optional[str]表示这个形参你只能传递str类型的值或者是None,注意,不能不传
foo(x='x')
foo(x=None)
# foo()  # TypeError: foo() missing 1 required positional argument: 'x'

# 如果你想指定某个形参可传可不传,那么你可以为形参设置一个默认值,比如y参数
foo(x='x')  # 对于y参数,可以省略不传,函数内部就会采用默认值None,或者你指定的其他值
foo(x='x', y='y')  # 如果y参数你要传值,就传规定的str类型的值
foo(x='x', y=1)  # 当然,形参y你传其它类型,代码也不报错,就是不符合规范就是了

自定义类型

比如有的时候,我们需要接受的参数是个自定义的类对象,那么你就可以这么写:

python
class User(object):
    def __init__(self, user: str, age: int):
        self.user = user
        self.age = age


def bar(user: User) -> None:
    print(user, user.user, user.age)


bar(User('张开', 18))  # <__main__.User object at 0x000001EF60B4FB20> 张开 18

在类中使用

下面用到的ClassVar是专门用于给类定义变量用的,类的实例化对象能用,但请不要去尝试修改。

参考官网:https://docs.python.org/zh-cn/3/library/typing.html?highlight=classvar#typing.ClassVar

python
from typing import ClassVar, Dict


class User(object):
    name: str = 'zhangkai'  # 实例变量,有默认值
    age: int  # 实例变量,没有默认值,要通过赋值后才能操作
    status: ClassVar[Dict[str, int]] = {}  # 类变量,只有通过类才能操作


user = User()
# 对于没有默认值的变量,要赋值之后,才能使用,否则报错
user.age = 18
print(user.name, user.status, user.age)

# 使用ClassVar标记的类变量,可以通过类直接进行操作
User.status = {'a': 1}
print(user.name, user.status, user.age)

# ide会提示你不能通过类的实例对象给类变量赋值
# 当然了,你要是硬来,也能赋值成功
user.status = {'b': 2}
print(user.name, user.status, user.age)

定义常量

再次强调,截止到目前2023/10/10,Python中没有常量的概念。

但是我们程序中对于那些定义后就不再改变的变量,通常以全大写的形式进行声明该变量,让其他看到该变量的同行,知道这个变量是常量,可以引用,但不要修改。

所以在类型注解中,也可以通过Final来进行对常量进行注解。

参考官网:https://docs.python.org/zh-cn/3/library/typing.html?highlight=classvar#typing.Final

python
from typing import Final

MAX_SIZE: Final = 9000
MAX_SIZE += 1  # ide会提示你MAX_SIZE是Final的,不应该对其进行重新赋值

参考:https://www.cnblogs.com/poloyy/p/15145380.html l>