Python中的类型提示是一种特殊的语法,这种语法能够显式声明一个变量的类型。通过显式声明变量类型,不仅使得代码可读性变高了,还能够让编辑器为我们编码提供更多的支持。

一、类型声明
类型声明主要作用在函数的参数以及返回值上。
1、简单类型
简单类型包含str
、int
、float
、bool
、bytes
五种类型,这些类型都是python标准类型。以下是一个例子:
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
return item_a, item_b, item_c, item_d, item_d, item_e
2、泛型类型
Python中的dict
、list
、set
以及tuple
类型都是能够包含其它类型值的数据结构,可以直接使用在方法参数上对参数进行标记:
def test(list1: list):
print(list1)
但是这种方式不能有效标记出来list列表中每一项的类型(3.9+版本已经解决这个问题),可以使用typing
模块解决这个问题。
List
接下来让我们定义一个str类型的list列表
在3.9+版本,可以直接使用list[type]
,这表示列表中每个元素都是type类型:
def process_items(items: list[str]):
for item in items:
print(item)
但是更通用的是使用typing模块,这种在3.8+版本均通用:
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
Tuple
Tuple是typing模块的元组类型类, Tuple[int, int, str]表示元组有三个元素,分别是int、int、str类型。
在3.9+版本:
def process_items(items_t: tuple[int, int, str]):
return items_t
在3.8+版本:
from typing import Tuple
def process_items(items_t: Tuple[int, int, str]):
return items_t
Set
Set是typing模块的集合类型类,Set[int]表示集合中的每个元素都是int类型。
3.9+版本:
def process_items(items_s: set[bytes]):
return items_s
3.8+版本:
from typing import Set
def process_items(items_s: Set[bytes]):
return items_s
Dict
Dict是typing模块的字典类型类,Dict[int,str]表示字典中的key都是int类型,value都是str类型。
3.9+版本:
def process_items(prices: dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
3.8+版本:
from typing import Dict
def process_items(prices: Dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
Union
一个变量可能有几种类型,比如int、str,可以使用typing 模块的Union类表示该变量是某几个类型的其中一个类型。比如Union[int,str]表示可能是int类型,也可能是str类型,但是只可能是其中一种。在3.10版本以前只能使用Union类来表示这种行为,3.10版本引入了新语法,str|int
即可表示Union[str,int],两者是等价的。
3.6+版本:
from typing import Union
def process_item(item: Union[int, str]):
print(item)
3.10+版本:
def process_item(item: int | str):
print(item)
Possibly None
在 Python 中,None
是一个特殊的单例对象,它是 NoneType
类型的唯一实例,用于表示“空”或“无值”。我们定义一个变量:name,它可能是str类型,也可能是None类型。在3.6+版本,可以使用typing模块的Optional来表达这种语义:
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
Optional[Something]
实际上等价于Union[Something, None]
,它是一种简化写法,在3.10+版本,可以更进一步写成Something | None
。
3.6+版本Union写法:
from typing import Union
def say_hi(name: Union[str, None] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
3.10+新语法更简洁:
def say_hi(name: str | None = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
疑问1:None是NoneType类型,Union[str, None] = None的写法不对吧,应该写成Union[str, NoneType] = None才对。
答:在 Python 的类型注解中,None
和 NoneType
实际上是等价的,None
可以直接代表 NoneType
,因此 Union[str, None]
和 Union[str, NoneType]
语义上是完全相同的。另外NoneType需要额外导入from types import NoneType
,所以使用Union[str, None]
写法更加简洁。
疑问2:name:Optional[str]=None
写法是否可以简化写成name:str = None
答:不可以,Optional[str]=None实际上就是Union[str,None]=None,两个None的意思不一样,不可省略。Union[str, None]
或 Optional[str]
明确表示 name
可以是字符串或 None
,语义表达更明确;而=None则是为它赋值了默认值为None,这样可以在不传name参数的时候为name赋予默认值None。实际上None是NoneType类型,为str类型的参数赋值None可能会出现类型不匹配的问题。
疑问3:Optional[str]和Union[str,None],应该使用哪一种写法?
我认为应当使用Union[str,None]这种写法,这种写法虽然更繁琐一些,但是它具有更高的可读性;Optional[str]会给人一种错觉,它是可有可无的,而实际上并非这样,它必须有值而且是str或者None的一种。
举个例子:
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
name参数是Optional(可选的),但是实际上它并非可选的(Optional),如果直接调用say_hi() 则会报错,因为name参数未传递。
二、自定义类类型
这个比较简单了,我们自定义一个类Person:
class Person:
def __init__(self, name: str):
self.name = name
在某个方法传递Person类的实例one_person,则可以这样写:
def get_person_name(one_person: Person):
return one_person.name
在编辑器中能得友好的提示:

三、Pydantic models
Pydantic 是一个基于 Python 类型注解的数据验证和解析库,广泛应用于 API 开发、配置管理等领域。使用前需要先安装依赖:
pip install pydantic
看一看Pydantic的基本使用:
from datetime import datetime
from typing import Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
使用时要继承BaseModel类,每个字段可以都使用类型提示加以说明,这样在创建实例的时候Pydantic会将输入自动转换为合适的类型值。比如输入的id是字符串类型的123,由于User类定义的id是int类型,所以会被自动转换为int类型。
更多Pydantic的使用可以参考官方文档:https://docs.pydantic.dev/2.3/usage/models/
四、使用元数据注解的类型提示
在Python中可以使用Annotated
提供类型提示功能的同时提供元数据信息。
在3.8+版本,Annotated需要引入typing_extensions
模块才能使用:
from typing_extensions import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
但是在3.9+版本,Annotated成为了标准库的一部分,所以可以通过typing模块直接导入。
from typing import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
Annotated的第一个参数是类型,剩余其它参数都是metadata数据。对于Python来说,它不会对Annotated做任何事情,剩余的元数据信息则在不同的框架中有不同的作用,比较典型的比如在langchain框架中:
import json
from typing import List
from langchain_core.tools import tool
from typing_extensions import Annotated
@tool
def tool(
a: Annotated[int, "scale factor"],
b: Annotated[List[int], "list of ints over which to take maximum"],
) -> int:
"""Multiply a by the maximum of b."""
return a * max(b)
if __name__ == '__main__':
print(f"Name: {tool.name}")
print(f"Description: {tool.description}")
print(f"args schema: {json.dumps(tool.args, indent=4)}")
print(f"returns directly?: {tool.return_direct}")
以上代码示例中定义了一个tool,a和b的元数据信息会被langchain框架解析用于说明两个参数的作用并提交给大模型,以让大模型理解两个参数该如何传递。
五、参考文档
https://fastapi.tiangolo.com/python-types/
注意:本文归作者所有,未经作者允许,不得转载