Python类型提示简介

Published on 2025-06-05 13:40 in 分类: 博客 with 狂盗一枝梅
分类: 博客

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

动画43_resize

一、类型声明

类型声明主要作用在函数的参数以及返回值上。

1、简单类型

简单类型包含strintfloatboolbytes 五种类型,这些类型都是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中的dictlistset以及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 的类型注解中,NoneNoneType 实际上是等价的,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

在编辑器中能得友好的提示:

image06

三、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/


#python
复制 复制成功