一.什么是类型注解
类型注解(Type Annoations)是Python中一种为变量、函数参数、返回值等显示声明其预期数据类型的语法机制,它的主要目的不是强制类型检查(Python本身仍然是动态类型语言),而是:
- 提高代码可读性和可维护性
- 帮助开发工具(如IDE)提供更好的智能提示和错误检测
- 支持静态类型检查工具(如mypy,pyright)在运行前发现潜在bug
二.基本语法示例
1.变量类型注释
python
name: str = "Alice"
age: int = 30
height: float = 1.75
is_student: bool = True
这里的: str、: int、: float、: bool 就是类型注释,说明这个变量应该是什么类型
注意:Python不会在运行时阻止你写" age = '30' ",但是Vs code或mypy会警告你
2.函数参数和返回值注解
python
def greet(name: str) -> str:
return "Hello, " + name
def add(a: int, b: int) -> int:
return a + b
name: str 表示参数name的类型应该是字符串
-> str 表示greet函数的返回值类型应该是字符串
3.复杂类型注解(需从typing模块导入)
python
from typing import List, Dict, Optional
def process_items(items: List[str]) -> Dict[str, int]:
return {item: len(item) for item in items}
def find_user(user_id: int) -> Optional[str]: # 可能返回 str 或 None
if user_id == 1:
return "Alice"
return None
注:在Python3.9+中,可以直接用内置类型如list[int]、dict[str, int],无需导入List、Dict
三.补充知识:动态类型语言
1.概念
动态类型语言(Dynamically Typed Language ),是指在程序运行时(runtime)才能确定变量类型的编程语言。
与之相对的是静态类型语言,如C、C++、Java,它们在编译时就必须明确每个变量的类型
2.动态类型语言的核心特点:
在动态类型语言中:
- 你声明一个变量,不需要指定它的类型
- 同一个变量可以在程序运行过程中指向不同类型的值
- 类型检查发生在运行时,而不是在写代码或编译时
3.动态类型语言的优点
| 优点 | 说明 |
|---|---|
| 灵活、简洁 | 写代码快,不用写冗长的类型声明 |
| 快速原型开发 | 适合脚本、自动化、实验性项目 |
| 多态天然支持 | 同一个函数可以处理多种类型(配合鸭子类型) |
鸭子类型(Duck Typing) :
"如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。"
不关心对象是什么类型,只关心它有没有需要的方法或属性
4.动态类型语言的缺点
| 缺点 | 说明 |
|---|---|
| 运行时错误风险高 | 比如对字符串调用 .append(),只有运行到那行才会报错 |
| IDE 智能提示弱(无注解时) | 不知道变量是什么类型,难以自动补全 |
| 大型项目维护难 | 函数参数到底该传什么?返回什么?不看文档很难知道 |
正因为这些缺点,Python从3.5开始引入了类型注解,让开发者可以自愿添加类型信息,兼顾灵活性和安全性
5.总结
动态类型语言 = 变量没有固定类型,值才有类型; 类型检查推迟到程序运行时进行。
它牺牲了一部分安全性和性能,换来了极大的灵活性和开发效率。通过类型注解 + 静态检查工具(如mypy),现代开发者可以借助类型注解实现静态类型的好处,实现两全其美。
理解:为什么会牺牲一部分安全性和性能
1.牺牲安全性
因为是动态类型,在程序运行中,可能得到非法的类型或者与开发者想要的类型不一致的类型,可能引起报错
如在Java中:
java
// Java(静态类型)
String name = "Alice";
int len = name.length(); // ✅ 正确
int result = len + "world"; // ❌ 编译错误!不能把 int 和 String 相加
在程序运行之前,编译器就会告诉开发者哪里出错了
在Python中:
python
# Python(动态类型)
name = "Alice"
len_val = len(name) # ✅ 没问题
result = len_val + "world" # ❌ 运行到这里才报错:TypeError
如果这段代码藏在某个 rarely-called 的分支里,可能上线几天后才崩溃!
简而言之,牺牲一部分安全性指的就是:运行时报错风险增加
2.牺牲性能
动态类型语言的性能损失主要来自一下几点:
- 每次操作都要检查类型(运行时开销)
在Python中,当你写a+b时,解释器必须先查看a和b是什么类型,看它们是否支持+操作,然后再调用对应的__add__方法,
而在C++中,编译器直接生成一条加法指令,无任何检查!
- 无法进行激进的编译优化
编译器优化依赖"确定性"。例如:
- 内联函数(Inlining):如果知道函数参数类型,可以直接把函数体展开。
- 向量化(SIMD) :对整数数组做批量运算,需要知道每个元素都是
int。 - 内存布局优化 :静态类型语言可以把多个
int紧凑排列;而 Python 的list存的是指向对象的指针,每个对象还有类型头、引用计数等额外信息。
- 对象内存开销大
Python的int类型对象不是简单的4字节整数,而是一个完整的对象
Python中的10可能占28字节以上,而在C中10只占4字节
四.相关模块
python
from __future__ import annotations
from typing import Optional
from future import annotations
它的核心作用是:让所有类型注解自动变成字符串形式(延迟求值)
背景问题:前向引用(Forward Reference),在定义类时,如果类的方法或属性要用到这个类自己,就会出现名字还没有定义的问题
如:
python
class TreeNode:
def __init__(self, left: TreeNode): # ❌ NameError: name 'TreeNode' is not defined
self.left = left
因为TreeNode类还在定义中,Python还没有把它加入命名空间,所以不能直接用
解决方法:
1.传统方法:用字符串
python
class TreeNode:
def __init__(self, left: 'TreeNode'): # ✅ 字符串,不会立即求值
self.left = left
2.更优雅的方法:
python
from __future__ import annotations
class TreeNode:
def __init__(self, left: TreeNode): # ✅ 现在可以直接写,不会报错!
self.left = left
from typing import Optional
作用:提供Optional[T] 类型,表示T或None
为什么需要:在类型系统中,"可能为空"是一个重要的信息
比如二叉树的子节点可能是另一个节点,也可能是None(表示空)
python
# 没有类型注解 → 别人不知道 left 可能是 None
def __init__(self, val, left=None):
self.left = left
有了Optional后,可以明确表达
python
from typing import Optional
class TreeNode:
def __init__(self, val: int, left: Optional[TreeNode] = None):
self.left = left # 类型是 TreeNode 或 None
等价写法:
python
# Python 3.10 起,Optional[T] 等价于 T | None
left: TreeNode | None = None
综合案例:
python
from __future__ import annotations # 允许在注解中直接写 TreeNode
from typing import Optional # 用于表示 "TreeNode 或 None"
class TreeNode:
def __init__(
self,
val: int,
left: Optional[TreeNode] = None, # 等价于 left: TreeNode | None
right: Optional[TreeNode] = None
):
self.val = val
self.left = left
self.right = right
如果Python>=3.10,也可以:
python
from __future__ import annotations
class TreeNode:
def __init__(
self,
val: int,
left: TreeNode | None = None,
right: TreeNode | None = None
):
self.val = val
self.left = left
self.right = right
注:要么导入annotations要么就加上字符串
如:
python
class TreeNode:
# 注意:如果在left和right的类型注释中不加引号,当Python解释器执行到这一行时,会尝试查找TreeNode这个变量/类,但此时TreeNode类还在创建中,并没有完成创建
# 这就是所谓的前向引用问题:在类内部引用了它自己
def __init__(self, val: int, left: 'TreeNode | None' = None, right: 'TreeNode | None' = None):
self.val = val
self.left = left
self.right = right