类型标注 = 给变量、参数、返回值写上类型说明。
它不会在运行时强制检查,主要给 ide / 类型检查工具用,用来:
- 写代码时自动补全、提示参数
- 提前发现「传错类型」这类问题
为什么需要
没有标注时,调用方不知道该传什么,也容易写出「能跑但不符合预期」的代码:
# 问题:参数类型不明确
def add(a, b):
return a + b
# 调用者不知道应该传什么类型
add(1, 2) # 3
add("1", "2") # "12" —— 这也是合法的,但可能不是预期行为
add([1], [2]) # [1, 2] —— 同样合法
# 没有类型提示,难以在编码时发现错误
基础写法
变量
# 声明变量的类型 name: str = "alice" age: int = 25 pi: float = 3.14 is_active: bool = true # 没有初始值 value: int value = 10 # python 是动态语言,类型标注不会强制约束 x: int = "hello" # 不会报错,但类型检查工具会提示
要点:写了 x: int,运行时照样可以塞字符串;要靠类型检查工具(或 ide)才会报警。
函数
格式:参数: 类型,返回值写在 -> 后面。
def greet(name: str, age: int) -> str:
"""函数参数和返回值的类型标注"""
return f"{name} 今年 {age} 岁
# 调用
greet("alice", 25) # 正确
greet("alice", "25") # 运行不会报错,但类型检查会警告
如果函数永远不会正常返回(比如直接退出程序),用 noreturn:
from typing import noreturn
def exit_program() -> noreturn:
"""表示函数永远不会正常返回"""
import sys
sys.exit(1)
常用复合类型
optional 和 union
optional:可能有值,也可能没有
optional[x] = 要么是 x,要么是 none。
下面三种写法意思完全一样:
optional[str] union[str, none] str | none # python 3.10+
典型场景:查找用户 —— 找到返回名字,找不到返回 none:
from typing import optional, union
# optional:值可以是某个类型,也可以是 none
def find_user(user_id: int) -> optional[str]:
"""返回用户名,找不到时返回 none"""
if user_id <= 0:
return none
return f"user_{user_id}"
调用时先判断是不是 none,再当字符串用:
name = find_user(1)
if name is none:
print("没找到")
else:
print(name) # 这里才是确定的 str
union:多种类型之一
union[a, b]:可以是 a,也可以是 b。
# union:值可以是多种类型之一
def parse_value(value: str) -> union[int, float, str]:
"""尝试将字符串转换为数字,失败则返回原字符串"""
try:
if "." in value:
return float(value)
return int(value)
except valueerror:
return value
新写法也可写成:int | float | str(python 3.10+)
容器类型
在 list、dict 等后面用 [] 写明里面装什么:
from typing import list, dict, tuple, set
# 列表:元素类型
scores: list[int] = [85, 90, 78]
names: list[str] = ["alice", "bob", "charlie"]
# 字典:键类型, 值类型
student_scores: dict[str, int] = {
"alice": 85,
"bob": 90,
}
# 元组:固定长度,每个位置类型可不同
point: tuple[int, int] = (10, 20)
person: tuple[str, int, bool] = ("alice", 25, true)
# 集合:元素类型
tags: set[str] = {"python", "typing", "type-hints"}
any 和类型别名
- any:随便什么类型都行(等于几乎不检查,少用)
- 类型别名:给复杂类型起个好记的名字
from typing import any, typealias
# any:任意类型,相当于没有类型约束
def log_data(data: any) -> none:
print(f"数据: {data}")
# 类型别名,让复杂类型更易读
vector: typealias = list[float]
matrix: typealias = list[list[float]]
def dot_product(v1: vector, v2: vector) -> float:
"""计算两个向量的点积"""
return sum(a * b for a, b in zip(v1, v2))
类里怎么标
- 方法参数、返回值照常写
- self:返回「自己这个类型的实例」
- 类还没定义完就要引用自己时,可写成字符串 "point"
from typing import self
class point:
def __init__(self, x: float, y: float) -> none:
self.x = x
self.y = y
def move(self, dx: float, dy: float) -> self:
"""返回移动后的新点"""
return point(self.x + dx, self.y + dy)
def distance_to(self, other: "point") -> float:
"""计算到另一个点的距离"""
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
# 使用
p1 = point(0, 0)
p2 = point(3, 4)
print(p1.distance_to(p2)) # 5.0
泛型
为什么需要泛型?
假设有一个函数,取出列表的第一个元素:
# 不用泛型:返回类型丢失了
def get_first(items):
return items[0]
# 你无法知道返回的到底是什么类型
有了泛型:输入什么类型,输出就是什么类型,类型信息不会丢。
传统写法(python 3.12 之前)
需要两个核心组件:typevar 和 generic。
typevar — 类型变量
t 代表「未来某个具体类型」,实际使用时才会被确定(类似数学里的未知数 x):
from typing import typevar
t = typevar('t') # 定义一个类型变量
泛型函数
from typing import typevar, list
t = typevar('t')
def get_first_item(lst: list[t]) -> t:
return lst[0]
# 调用时,类型自动推断
num = get_first_item([1, 2, 3]) # num 的类型是 int
text = get_first_item(['a', 'b', 'c']) # text 的类型是 str
泛型类
from typing import typevar, generic
t = typevar('t')
class box(generic[t]):
def __init__(self, content: t):
self.content = content
def get(self) -> t:
return self.content
# 使用
int_box = box(42) # box[int]
str_box = box("hello") # box[str]
现代写法(python 3.12+)
可以直接在类 / 函数名后面声明类型参数,不必再单独写 typevar + generic:
# 以前
from typing import typevar, generic
t = typevar('t')
class box(generic[t]):
def get(self) -> t: ...
# 现在(python 3.12+)
class box[t]:
def get(self) -> t: ...
# 函数也可以这样写
def get_first[t](lst: list[t]) -> t:
return lst[0]
更接近 java / c# 的写法,类型参数不再游离在类 / 函数外面。
typevar 的三种约束方式
1. 无约束(任意类型)
t = typevar('t') # 可以是任何类型
2. bound 约束(必须是某类型的子类)
s = typevar('s', bound=str) # 必须是 str 或其子类
def print_capitalized(x: s) -> s:
print(x.capitalize())
return x
3. 值约束(只能是指定的几种类型之一)
a = typevar('a', str, bytes) # 只能是 str 或 bytes
def concatenate(x: a, y: a) -> a:
return x + y
concatenate("hello", "world") # ok,返回 str
concatenate(b"foo", b"bar") # ok,返回 bytes
# concatenate("foo", b"bar") # 类型检查报错,不能混用
日常写业务代码,多数时候用 list[int]、str | none 就够了。
自己写「输入什么类型、输出就保持什么类型」的函数 / 类时,才需要泛型。
callable:参数本身是函数
callable[[参数类型...], 返回类型] 用来标注「回调函数长什么样」。
from typing import callable
def execute_callback(
callback: callable[[int, int], int],
a: int,
b: int
) -> int:
"""执行回调函数"""
return callback(a, b)
# 使用
result = execute_callback(lambda x, y: x + y, 3, 5)
print(result) # 8
读法:callable[[int, int], int] = 接收两个 int,返回一个 int 的函数。
实际能干什么
1. 用 typeddict 描述「字典长什么样」
普通 dict 太松;typeddict 可以规定每个 key 的类型:
from typing import typeddict
class userresponse(typeddict):
"""api 返回的用户数据结构"""
id: int
name: str
email: str
is_active: bool
def get_user(user_id: int) -> userresponse:
return {
"id": user_id,
"name": "alice",
"email": "alice@example.com",
"is_active": true,
}
某个字段可以不提供时,用 notrequired:
from typing import typeddict, notrequired
class product(typeddict):
id: int
name: str
description: notrequired[str] # 可选字段,可以不提供
# 两种写法都合法
p1: product = {"id": 1, "name": "手机"}
p2: product = {"id": 2, "name": "电脑", "description": "高性能笔记本"}
反过来,希望默认所有字段都可选时,用 total=false:
class config(typeddict, total=false):
debug: bool
retry_times: int
timeout: float
# 所有字段都可以不提供
c: config = {}
到此这篇关于python中类型标注的实现示例的文章就介绍到这了,更多相关python 类型标注内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论