Python的类型注解

一.什么是类型注解

类型注解(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.牺牲性能

动态类型语言的性能损失主要来自一下几点:

  1. 每次操作都要检查类型(运行时开销)

在Python中,当你写a+b时,解释器必须先查看a和b是什么类型,看它们是否支持+操作,然后再调用对应的__add__方法,

而在C++中,编译器直接生成一条加法指令,无任何检查!

  1. 无法进行激进的编译优化

编译器优化依赖"确定性"。例如:

  • 内联函数(Inlining):如果知道函数参数类型,可以直接把函数体展开。
  • 向量化(SIMD) :对整数数组做批量运算,需要知道每个元素都是 int
  • 内存布局优化 :静态类型语言可以把多个 int 紧凑排列;而 Python 的 list 存的是指向对象的指针,每个对象还有类型头、引用计数等额外信息。
  1. 对象内存开销大

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
相关推荐
岱宗夫up1 小时前
FastAPI进阶:从入门到生产级别的深度实践
python·信息可视化·fastapi
七夜zippoe1 小时前
图神经网络实战:从社交网络到推荐系统的工业级应用
网络·人工智能·pytorch·python·神经网络·cora
啊阿狸不会拉杆1 小时前
《计算机视觉:模型、学习和推理》第 1 章 - 绪论
人工智能·python·学习·算法·机器学习·计算机视觉·模型
was1721 小时前
Python 自动化实践:Typora 自定义上传接口与兰空图床集成
python·自动化·typora·兰空图床
wjs20241 小时前
HTML URL 编码
开发语言
wjs20242 小时前
Lua 循环
开发语言
二十雨辰2 小时前
[python]-网络编程
python
Evand J2 小时前
matlab GUI制作界面的一些笔记(入门)
开发语言·笔记·matlab
我是大猴子2 小时前
Java面经
java·开发语言