
咱们写 Python 时是不是常遇到这些糟心事儿:
user.的时候,不知道后面有啥属性,只能瞎猜。这都是 Python “动态类型” 搞的 —— 写代码时灵活,但协作和维护时太费劲。今天咱们就吃透 “类型提示(Type Hinting)”,学会它,代码可读性、防错能力直接拉满,IDE 还能给你当 “导航”!
在讲怎么用之前,先明确它的价值,不然你可能觉得 “多写一行代码没必要”:
name: str一看就知道是字符串;int,你传了str,用工具(比如 mypy)提前就能查到,不用等运行崩溃;user.,IDE 直接列出user.id、user.name;简单说:类型提示不是给 Python 解释器看的(Python 运行时根本不看),是给 “人” 和 “工具” 看的,帮咱们少走弯路。
最入门的用法 —— 给变量加 “类型标签”,格式是变量名: 类型 = 值。比如你定义一个名字变量,直接标明是字符串,谁看都懂。
我把 Python 里最常用的基础类型整理成表格,连旧版本兼容写法都标了(Python 3.9 + 支持直接用list[int],3.9 以下要从typing导入List[int]):
数据类型 | Python 3.9+ 写法 | 旧版本写法(需导入) | 可运行代码示例 |
|---|---|---|---|
整数 | int | int |
|
字符串 | str | str |
|
浮点数 | float | float |
|
布尔值 | bool | bool |
|
列表(指定元素类型) | listint | from typing import List; Listint |
|
字典(指定键值类型) | dictstr, int | from typing import Dict; Dictstr, int |
|
元组(固定元素类型) | tuplestr, int | from typing import Tuple; Tuplestr, int |
|
集合(指定元素类型) | setint | from typing import Set; Setint |
|
肯定能!你把下面代码复制到 Python 里跑,一点问题没有:
# Python 3.9+ 环境直接运行,3.9以下需按表格导入对应模块
# 1. 简单变量
age: int = 25
username: str = "zhangsan"
is_active: bool = True
height: float = 1.75
# 2. 复杂类型(列表、字典、元组)
scores: list[int] = [90, 85, 95]
user_score: dict[str, int] = {"math": 90, "english": 85}
user_info: tuple[str, int] = ("zhangsan", 25)
# 打印测试,完全能运行
print(f"用户名:{username},年龄:{age}")
print(f"数学成绩:{user_score['math']}")
print(f"用户信息:{user_info}")运行结果:
用户名:zhangsan,年龄:25
数学成绩:90
用户信息:('zhangsan', 25)有人会问:“我写了age: int,但给age赋值"25"(字符串),代码咋还能跑?”
因为 Python 是动态类型,类型提示只是 “提示”,不是 “强制约束”!解释器运行时根本不检查类型。想让它帮你查错,得用工具(比如mypy),后面会讲。
函数是协作中最容易出问题的地方 —— 别人不知道传啥参数、不知道返回啥值。给函数加类型提示,这问题直接解决。
格式很简单:
def 函数名(参数1: 类型1, 参数2: 类型2) -> 返回值类型:
比如写个加法函数,明确参数是int,返回值也是int:
# 加法函数:参数a、b都是int,返回值也是int
def add(a: int, b: int) -> int:
return a + b
# 调用测试:传int没问题
print(add(1, 2)) # 输出3
# 要是传字符串,虽然运行时会报错,但mypy工具能提前查到
# print(add("1", "2")) # mypy提示:参数应该是int,不是str实际写函数时,会遇到默认值、*args、**kwargs,这些也能加类型提示,看例子:
默认值跟在类型提示后面,比如给greeting加默认值"Hello":
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
# 调用测试,都能运行
print(greet("张三")) # 输出:Hello, 张三!
print(greet("李四", "Hi")) # 输出:Hi, 李四!*args本质是元组,所以用tuple[类型],比如接收多个整数求和:
def sum_nums(*args: int) -> int:
return sum(args)
# 调用测试
print(sum_nums(1, 2, 3)) # 输出6
# print(sum_nums(1, "2", 3)) # mypy提示:不能传str**kwargs本质是字典,用dict[键类型, 值类型],比如接收用户信息并打印:
def print_user(**kwargs: str) -> None: # 返回值是None,就写-> None
for key, value in kwargs.items():
print(f"{key}: {value}")
# 调用测试:键值都是str,没问题
print_user(name="张三", age="25", city="北京")
# print_user(name="李四", age=25) # mypy提示:age应该是str,不是int函数返回多个值时,Python 会自动打包成元组,类型提示直接写tuple[类型1, 类型2]:
# 返回用户名和年龄,类型分别是str和int
def get_user() -> tuple[str, int]:
return "张三", 25
# 调用测试,能正常解包
username, age = get_user()
print(username, age) # 输出:张三 25平时写类,要写__init__、__repr__这些方法,又麻烦又容易错。用dataclass(Python 3.7+)配合类型提示,能自动生成这些方法,代码还特别干净。
先看普通类的麻烦:
# 普通类:要自己写__init__,还得写__repr__才能打印好看
class User:
def __init__(self, id: int, name: str, age: int):
self.id = id
self.name = name
self.age = age
def __repr__(self):
return f"User(id={self.id}, name={self.name}, age={self.age})"
# 实例化
user1 = User(1, "张三", 25)
print(user1) # 输出:User(id=1, name=张三, age=25)再看 dataclass + 类型提示的简洁版:
from dataclasses import dataclass # 先导入dataclass
# 加@dataclass装饰器,写类型提示就行,不用写__init__和__repr__
@dataclass
class User:
id: int # 字段1:int类型
name: str # 字段2:str类型
age: int # 字段3:int类型
city: str = "北京" # 带默认值的字段
# 实例化:直接传参数,不用管__init__
user1 = User(1, "张三", 25)
user2 = User(2, "李四", 30, "上海")
# 打印测试:自动有__repr__,格式还好看
print(user1) # 输出:User(id=1, name=张三, age=25, city='北京')
print(user2) # 输出:User(id=2, name=李四, age=30, city='上海')
# 访问属性:正常用,IDE还能补全
print(user1.name) # 输出:张三还能给@dataclass加参数,实现更多功能,比如 “冻结类”(实例化后不能改属性):
from dataclasses import dataclass
# frozen=True:实例属性不能修改,改了会报错
@dataclass(frozen=True)
class User:
id: int
name: str
user1 = User(1, "张三")
# user1.name = "李四" # 运行报错:dataclasses.FrozenInstanceError: cannot assign to field 'name'再比如 “排序”(按字段自动排序):
from dataclasses import dataclass, field
@dataclass(order=True) # order=True:支持比较排序
class Student:
# 用field(init=False):这个字段不参与__init__,靠后面计算
sort_index: int = field(init=False, repr=False)
name: str
score: int
# 定义排序规则:按score降序(分数高的排前面)
def __post_init__(self):
self.sort_index = -self.score
# 实例化多个学生
stu1 = Student("张三", 90)
stu2 = Student("李四", 85)
stu3 = Student("王五", 95)
# 排序测试:sorted会自动按score降序排
students = [stu1, stu2, stu3]
sorted_students = sorted(students)
print(sorted_students)
# 输出:[Student(name='王五', score=95), Student(name='张三', score=90), Student(name='李四', score=85)]实际开发中,经常遇到 “参数可以是 A 类型,也可以是 B 类型” 的情况,比如用户 ID 可以是字符串("123")也可以是整数(123),这时候就要用 “联合类型”;如果参数可以传None,就要用 “可选类型”。
格式:Python 3.10 + 支持类型1 | 类型2,旧版本用Union[类型1, 类型2](需从typing导入)。
比如写个函数,接收 “字符串或整数” 的用户 ID:
# Python 3.10+ 写法(推荐,更简洁)
def get_user_detail(user_id: str | int) -> None:
print(f"用户ID:{user_id},类型:{type(user_id)}")
# Python 3.10以下写法(需导入Union)
# from typing import Union
# def get_user_detail(user_id: Union[str, int]) -> None:
# print(f"用户ID:{user_id},类型:{type(user_id)}")
# 调用测试:传str或int都没问题
get_user_detail("123") # 输出:用户ID:123,类型:<class 'str'>
get_user_detail(123) # 输出:用户ID:123,类型:<class 'int'>
# get_user_detail(True) # mypy提示:不能传bool(虽然bool是int子类,但类型提示会限制)格式:Python 3.10 + 支持类型 | None,旧版本用Optional[类型](需从typing导入),它等价于Union[类型, None]。
比如写个发送邮件的函数,主题(subject)可选,不传就是None:
# Python 3.10+ 写法
def send_email(to: str, subject: str | None = None) -> None:
if subject:
print(f"给{to}发邮件,主题:{subject}")
else:
print(f"给{to}发邮件,无主题")
# Python 3.10以下写法
# from typing import Optional
# def send_email(to: str, subject: Optional[str] = None) -> None:
# ...
# 调用测试:传subject或不传都没问题
send_email("test@example.com") # 输出:给test@example.com发邮件,无主题
send_email("test@example.com", "Hello") # 输出:给test@example.com发邮件,主题:Hello别搞混了:
Optional[T] = Union[T, None],是 “特殊的 Union”,只能跟 None 组合;Union[T1, T2]是 “通用的”,可以组合任意多个类型(比如Union[str, int, float])。能用Optional就别用Union,比如Optional[str]比Union[str, None]更直观,别人一看就知道 “这个参数可选,能传 None”。
前面光说好处,现在实际讲讲 —— 加了类型提示,IDE 和调试会变多爽。
比如你定义了User类,实例化后写user.,IDE 会自动列出user.id、user.name这些属性,不用你记:
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
age: int
# 实例化user
user = User(1, "张三", 25)
# 写user.的时候,IDE会自动补全id、name、age,不用猜
print(user.name) # 直接选name,不用怕拼错如果没加类型提示,IDE 不知道user是什么类型,写user.只会显示通用方法,没法补全属性,很容易拼错(比如把name写成naem)。
调试时,你不用再用print(type(变量))看类型了 ——IDE 会直接显示变量的类型。比如用 PyCharm 调试:
user: User,调试时鼠标悬停在user上,会直接显示User(id=1, name='张三', age=25);object,你还得点进去看属性,浪费时间。前面说过,Python 运行时不检查类型提示,但用mypy工具能提前查到类型错误。比如:
pip install mypyint类型的变量赋值str):# 代码文件:test.py
age: int = "25" # 类型提示是int,但赋值了strmypy ``test.pytest.py:1: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
Found 1 error in 1 file (checked 1 source file)这样,你不用等代码运行,就能提前修复错误,尤其是大型项目,能省很多调试时间。
学类型提示时,很多人会犯这些错,我整理了最常见的 5 个,附解决方法:
问题:比如写age: int = "25",代码能运行,但实际是错的,mypy 才会提示。
原因:Python 不强制检查类型提示,只认 “动态类型”。
解决:每次写完代码,用mypy检查一遍,养成习惯。
问题:比如函数参数可以传str或None,写成Union[str](少了 None),或者str | int(错用 Union)。
解决:
Optional[str](或str | None);Union[str, int](或str | int)。问题:比如在类 A 里用类 B 当类型提示,但类 B 定义在类 A 后面,会报错。
# 错误示例:类B在类A后面定义,类A里用B当类型提示会报错
class A:
def __init__(self, b: B): # 报错:name 'B' is not defined
self.b = b
class B:
pass解决:用 “字符串类型提示”(把类名写成字符串),或者从__future__导入annotations:
# 方法1:字符串类型提示
class A:
def __init__(self, b: "B"): # 把B写成"B",不会报错
self.b = b
class B:
pass
# 方法2:导入annotations(Python 3.7+)
from __future__ import annotations
class A:
def __init__(self, b: B): # 直接写B,不会报错
self.b = b
class B:
pass问题:Python 3.9 以下用List[int],没导入from typing import List,会报错。
# 错误示例(Python 3.9以下)
scores: List[int] = [90, 85] # 报错:name 'List' is not defined解决:
from typing import List, Dict, Tuple;list[int]、dict[str, int],不用导入。问题:比如写def add(a: int, b: int):,漏了-> int,别人不知道返回啥类型。
解决:所有函数都要写返回值类型,哪怕返回None(写-> None),养成规范。
学完了,面试时遇到这些问题别慌,我给你整理了 “大白话回答 + 例子”:
回答:
主要是 3 个好处:
user: User一看就知道是 User 类实例; 比如写个函数def send_email(to: str, subject: Optional[str] = None) -> None,别人一看就知道to是字符串,subject可选,返回 None,很清晰。
回答:
Optional 是 Union 的 “特殊情况”:
Optional[str]就是 “字符串或 None”;Union[str, int]就是 “字符串或整数”。 用的时候,能选 Optional 就选,更直观。比如函数参数可选(能传 None),用Optional[str]比Union[str, None]清楚。
回答:
因为*args本质是元组,**kwargs本质是字典,所以:
*args:用tuple[类型],比如*args: tuple[int](接收多个整数);**kwargs:用dict[键类型, 值类型],比如**kwargs: dict[str, str](接收键值都是字符串的参数)。举个例子:
def sum_nums(*args: int) -> int:
return sum(args)
def print_info(**kwargs: str) -> None:
for k, v in kwargs.items():
print(f"{k}: {v}")回答:
dataclass 配合类型提示很简单:加@dataclass装饰器,然后给每个字段写类型提示,不用写__init__和__repr__,Python 会自动生成。
比普通类好 2 点:
__init__里给属性赋值);__repr__,打印实例时格式好看,不用自己写。例子:
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
age: int
user = User(1, "张三", 25)
print(user) # 自动输出User(id=1, name=张三, age=25)回答:
不会!因为 Python 解释器运行代码时,根本不处理类型提示 —— 类型提示只是 “注释性的信息”,不是代码逻辑的一部分。
比如你写age: int = 25,Python 运行时只认age = 25,不管: int。所以加类型提示不会让代码变慢,放心用。
回答:
主要是 “不用导入 typing 模块了”,更简洁:
List[int]、Dict[str, int],必须导入from typing import List, Dict;list[int]、dict[str, int],跟普通类型写法一致,不用导入; 另外,3.10 + 支持类型1 | 类型2(比如str | int),代替旧版本的Union[str, int],更直观。
类型提示不是 “花架子”,是提升代码质量的实用工具:
刚开始可能觉得麻烦,但写习惯了会发现 —— 维护代码时省了太多时间!从今天开始,把类型提示用在你的 Python 项目里吧~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。