about
如果你能访问youtube且英文能力还行的话,可以参考Guido关于type hints的演讲:https://www.youtube.com/watch?v=2wDvzy6Hgxg
参考:
从Python3.5和PEP484开始,引入了类型注解(type hints)机制。
主要作用:
- 类型检查,防止运行时出现参数和返回值类型、变量类型不符合。
- 三方ide的在类型检查、代码提示等功能更加方便。
- 作为开发文档附加说明,方便使用者调用时传入和返回参数类型。
总之,我们日常开发中仍然可以不用考虑这个类型注解,一切照旧。
但现在有了这个类型注解,我们可以更好的规范代码,比如加上类型注解之后,我们能一眼就能看懂,某个函数接受参数的类型,某些变量的类型是什么,有没有返回值,返回值的类型等。
user: str = ''
def foo(name: str, age: int) -> str:
return name
比如上面的foo函数,期望传递的name参数是str类型,age参数传递的整型,而返回值则是str类型。这样的话,开发人员看到这段代码就能很好的使用这个函数了。
而且加上类型注解,你的ide会更贴心
但是,你愣是不按照类型注解期望的方式传递参数,当然可以,代码照常运行......至于报错或者出bug了,就需要我们自己排除了。
为啥要学习这个类型注解,因为现在有些框架比如fastapi结合pydantic就能实现加强类型注解的作用,不按照规定传参,就给你错误提示,再加上其他模块提供的调试功能,大大提高了开发和调试效率。
所以,我们要对类型注解这块,要学习,要了解。
基础的类型注解
常用的基础的类型注解有下面这些,且无需导入任何模块,就可以直接使用的。
a: int = 1
b: str = ''
c: float = 3.14
d: list = []
e: dict = {}
f: set = set()
g: tuple = ()
当然,内置模块typing
中,罗列了几乎我们开发中常见的那些类型,比如下面这几个List
、Dict
就等价于内置的list
、dict
,但支持一些细节实现,我们下面会讲。
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
模块无需下载,但是使用之前必须先导入。
概念:泛型
拿上面的List
和list
来举例,我们定义了变量d: list = []
,且它的类型是列表,但问题来了,列表中可以存放各种类型的数据,那你这个变量d
指向的列表中,到底存放什么样的类型?你总要说一下吧,所以,泛型出来了,也就是List
,可以用这个List
在list
的基础上,对列表中的元素进一步进行扩展说明。所以,我们可以将List
称为是list
的泛型。具体用法下文见。
类型注解的使用规范
类型注解在 PEP 8 中,具体的格式是这样规定的:
- 在声明变量类型时,变量后方紧跟一个冒号,冒号后面跟一个空格,再跟上变量的类型,再跟上一个空格,空格后面再跟上等号,等号后面再来一个空格,最后就是其值,具体就是这样的
a: int = 1
。 - 在声明方法返回值的时候,箭头左边是方法定义,箭头右边是返回值的类型,箭头左右两边都要留有空格,具体用法
def foo(name: str, age: int) -> str:
。
省略号...
省略号...
在类型注解时也会用到,它在这里通常用来表示同类型的可变长度的多个元素的类型。
List、Tuple、Sequence
List、Tuple
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
Sequence
是 collections.abc.Sequence
的泛型,在某些情况下,我们可能并不需要严格区分一个变量或参数到底是列表 list 类型还是元组 tuple 类型,我们可以使用一个更为泛化的类型,叫做 Sequence
,其用法类似于 List,如:
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 推荐用于注解参数。它们的使用方法都是一样的,其后跟一个中括号,中括号内分别声明键名、键值的类型。
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
用于注解参数。它们的使用方法都是一样的,其后跟一个中括号,里面声明集合中元素的类型。
from typing import Set, AbstractSet
def bar(m: AbstractSet[str]) -> Set[str]:
print(m)
return set(m)
bar({'a', 'b'})
上例将 Set 用作了返回值类型注解,将 AbstractSet 用作了参数类型注解。
NoReturn
当一个方法没有返回结果时,为了注解它的返回类型,我们可以将其注解为 NoReturn。
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。
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,可调用类型,它通常用来注解一个方法。
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 类型。
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))
默认值
对于注解类型的应用中,也可以设置默认值:
def foo(x: str = '张开', y: int = 18) -> None:
print(x, y)
# 因为有了默认值,可以不传,当然传,肯定就用你传的
foo()
foo('李开', 29)
Optional
Optional,意思是说这个参数可以为空或已经声明的类型,即 Optional[X] 等价于 Union[X, None]。 但值得注意的是,这个并不等价于可选参数,当它作为参数类型注解的时候,不代表这个参数可以不传递了,而是说这个参数可以传为 None。
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你传其它类型,代码也不报错,就是不符合规范就是了
自定义类型
比如有的时候,我们需要接受的参数是个自定义的类对象,那么你就可以这么写:
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
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
from typing import Final
MAX_SIZE: Final = 9000
MAX_SIZE += 1 # ide会提示你MAX_SIZE是Final的,不应该对其进行重新赋值