Python 注释你真的写对了吗?从单行到多行,从文档字符串到类型注解,一套全讲透(附真实项目规范实战)

目录


一、注释到底有什么用

先不说技术,说人话——注释就是你给未来的自己(或同事)留的纸条。你现在写的代码,三个月后回头看一眼,如果没有注释,大概率要花时间重新理解。

没有

你写代码

加注释
说明思路

三个月后
回来看代码

有注释?

秒懂
立刻上手改

盯着看了五分钟
:这特么是我写的?

注释在 Python 里干三件事:

作用 说明 例子
解释代码逻辑 说明"为什么这么写",不是"这段代码在干嘛" # 用快排而非冒泡,数据量 100w 条
标记 TODO / FIXME 标注待办事项和待修 Bug # TODO: 这里后续要加缓存
生成文档 Docstring 能被工具(Sphinx、pydoc)自动提取 函数下的三引号注释

二、单行注释:你每天写的最多的东西

Python 单行注释用 # 开头,从 # 到行尾都是注释内容,解释器直接跳过。

# 这是第一个 Python 程序 —— 一个单行注释
print("hello world!")

print("hello world!")  # 这也是一个单行注释,跟在代码后面

单行注释的几个好习惯:

# ✓ # 后面留一个空格,看起来舒服
# ✓ 注释写在上方,而不是同一行的末尾(除非注释很短)

# 计算用户购物车的总金额
total = sum(item.price for item in cart.items)

# ✗ 这种注释——废话,代码本身就说了
total = total + 1  # total 加 1

# ✓ 这种注释——解释了"为什么"
total = total + 1  # 凑单到 99 才能免运费,给用户加一个赠品

注释和代码之间留空行:

# 处理用户登录逻辑
# 这里用 bcrypt 加密,比 md5 安全
password_hash = bcrypt.hashpw(password, bcrypt.gensalt())

# 写入数据库
user.save()

习惯上 # 后面跟一个空格,注释内容跟代码之间也尽量保持合理的间距。


三、多行注释:三种写法及陷阱

Python 没有真正的"多行注释"语法。所谓的多行注释,其实是借用了字符串字面量

写法一:三个单引号(最常用)

'''
这是一个多行注释
可以写很多行
用三个单引号作为首尾
'''
print("hello world!")

写法二:三个双引号

"""
这也是一个多行注释
用三个双引号作为首尾
效果一样
"""
print("hello world!")

写法三:多个 #(实际更推荐)

# 这才是"真正"的多行注释
# 每行都以 # 开头
# 没有歧义,不会被当成字符串
print("hello world!")

三种写法对比:

写法 本质 被解释器看到吗 推荐度
''' 注释 ''' 字符串字面量 看到了,但不赋给变量,丢弃 ⭐⭐⭐
""" 注释 """ 字符串字面量 同上 ⭐⭐⭐
# 多行 真正的注释 不看到,直接跳过 ⭐⭐⭐⭐⭐

三引号的坑——它其实是字符串,不是真正的注释:

# ✗ 错误——缩进里的三引号会出问题
def foo():
    x = 1
    '''
    这段注释会导致 IndentationError
    因为它是一个字符串,不是注释
    缩进必须跟代码一致
    '''
    y = 2

# ✓ 正确——缩进跟代码一致
def foo():
    x = 1
    '''
    这段字符串字面量
    缩进正常,没问题
    '''
    y = 2

# ✓✓ 最佳——直接全用 #,没有歧义
def foo():
    x = 1
    # 这个是真正的注释
    # 解释器完全不碰它
    y = 2

三引号写多行注释方便,但它是"字符串"不是"注释",放在函数里缩进必须对。更稳妥的做法是用 # 一行一行写。


四、文档字符串(Docstring):Python 的注释规范

Docstring 是一种特殊的注释——写在函数/类/模块的第一行,用三引号包裹,能被 help() 函数和文档生成工具(Sphinx)识别。

函数的 Docstring:

def calculate_discount(price, rate):
    """
    计算商品打折后的价格

    Args:
        price: 原价,单位是元
        rate: 折扣率,0-1 之间的小数,如 0.85 表示 85 折

    Returns:
        打折后的价格,保留两位小数

    Raises:
        ValueError: 折扣率不在 0-1 之间时抛出

    Example:
        >>> calculate_discount(100, 0.85)
        85.0
    """
    if not (0 <= rate <= 1):
        raise ValueError("折扣率必须在 0-1 之间")
    return round(price * rate, 2)

类的 Docstring:

class ShoppingCart:
    """
    购物车类,管理用户的商品列表

    Attributes:
        user_id: 用户唯一标识
        items: 购物车中的商品列表
        created_at: 购物车创建时间

    Methods:
        add_item: 添加商品
        remove_item: 移除商品
        get_total: 计算总金额
    """
    def __init__(self, user_id):
        self.user_id = user_id
        self.items = []

模块的 Docstring:

"""
购物车模块
提供购物车的增删改查功能

作者: xxx
版本: 1.0
日期: 2024-01-01
"""

三种流行的 Docstring 风格:

风格 特点 适用场景
Google 风格 Args: / Returns: / Raises: 清晰直观,推荐新手用
Sphinx 风格 :param name: / :return: / :rtype: 配合 Sphinx 生成文档最强
NumPy 风格 Parameters 分段写 科学计算领域

如果你用 PyCharm,函数下打三个双引号然后回车——IDE 会自动生成 Docstring 模板(带 Args / Returns)。这也是为什么建议用 """...""" 而非 '''...''' 写 Docstring。


五、类型注解:注释的进阶玩法

Python 3.5 之后引入了类型注解(Type Hints)。严格来说它不是注释,但很多项目里把它当"给开发者看的注释"用:

# ✓ 没有类型注解——不知道 price 是什么类型
def calculate_discount(price, rate):
    return price * rate

# ✓ 加了类型注解——一眼就知道参数和返回值的类型
def calculate_discount(price: float, rate: float) -> float:
    return price * rate

# ✓ 复杂类型的注解
from typing import List, Dict, Optional

def get_user_orders(
    user_id: int,
    status: Optional[str] = None
) -> List[Dict[str, any]]:
    """
    获取用户的订单列表

    Args:
        user_id: 用户 ID
        status: 订单状态,None 表示查全部

    Returns:
        订单列表,每条订单是一个字典
    """
    pass

为什么用类型注解?

  • IDE 能自动补全和检查:传错类型,PyCharm 直接标黄
  • 给别人看的时候更清晰:不用翻代码就知道参数是什么类型
  • 配合 mypy 做静态检查:在 CI 里跑 mypy,类型不匹配直接报错

六、注释的书写规范与最佳实践

原则一:注释解释"为什么",不解释"是什么"

# ✗ 废话注释——代码本身已经说清楚了
x = x + 1  # 让 x 加 1

# ✓ 有意义的注释——解释了业务原因
x = x + 1  # 跳过表头行

# ✓✓ 更好的做法——把代码写得不需要注释
total_rows = data_rows + 1  # 加表头

原则二:TODO / FIXME / HACK 标注清晰

# TODO(zhangsan): 这里需要加 Redis 缓存,减少数据库查询压力
# FIXME: IS_REBUG 这里并发不安全,高并发下会出现幻读
# HACK: 临时方案,等第三方库修了 Bug 之后去掉这行

原则三:注释跟代码同步更新

代码改了注释一定要跟着改——过期的注释比没有注释还害人。

# ✗ 重命名了变量,注释还没改
# x 表示用户余额
user_balance = get_balance()  # 变量已经改名叫 user_balance 了

原则四:不要给显而易见的代码加注释

# ✗ 不需要——代码本身就是这句话
for item in items:  # 遍历每一个元素
    print(item)     # 打印出来

原则五:复杂逻辑必须加注释

# ✓ 复杂业务——不加注释谁都看不懂
# 满减计算逻辑:
# 1. 满 200 减 20,满 500 减 60
# 2. 优惠不可叠加,取最大
# 3. 会员额外 95 折,在满减之后再折
discount = max(
    (total // 200) * 20,
    (total // 500) * 60
)
final = (total - discount) * (0.95 if is_vip else 1.0)

原则六:注释统一用中文还是英文

团队内部项目——用中文没问题。开源项目建议用英文,国际化更容易。


七、IDE 里的注释快捷键

PyCharm / VS Code 通用快捷键:

快捷键 作用
Ctrl + / 注释/取消注释当前行
Ctrl + Shift + / 块注释(PyCharm 里用三引号包起来)

PyCharm 的高级用法:

# 在函数名上方输入 """ 然后回车
# PyCharm 自动补全 Docstring 模板:
def add(a, b):
    """
    
    :param a: 
    :param b: 
    :return: 
    """
    return a + b

VS Code:

# 安装 Python Docstring Generator 插件
# 在函数下输入 """ 自动生成 Google / Sphinx / NumPy 风格的 Docstring

八、实战:自动从注释生成文档

用 pydoc 从 Docstring 生成 HTML 文档:

# 文件:calculator.py
"""
计算器模块
提供基本的数学运算功能
"""

def add(a: float, b: float) -> float:
    """
    加法运算

    Args:
        a: 第一个加数
        b: 第二个加数

    Returns:
        两数之和
    """
    return a + b

def subtract(a: float, b: float) -> float:
    """
    减法运算

    Args:
        a: 被减数
        b: 减数

    Returns:
        两数之差
    """
    return a - b

生成文档:

# 在控制台看文档
python -m pydoc calculator

# 导出 HTML 文档
python -m pydoc -w calculator
# 生成 calculator.html

# 启动文档服务器(浏览器打开 http://localhost:1234)
python -m pydoc -p 1234

用 Sphinx 生成专业文档:

# 1. 安装 Sphinx
pip install sphinx

# 2. 初始化文档项目
sphinx-quickstart docs

# 3. 配置 conf.py 里加上 autodoc
# extensions = ['sphinx.ext.autodoc']

# 4. 生成 HTML
cd docs
make html

生成的文档长什么样子可以参考各大 Python 库(Requests、Flask)的官网——它们的文档基本都是这样自动生成的。


九、常见踩坑记录

坑 1:三引号"注释"在代码块中间报 IndentationError

def foo():
    x = 1
    '''
    这里缩进不对——会报错
    因为三引号是字符串不是注释
    '''
    y = 2

解决:要么把三引号的缩进对齐代码层级,要么直接用 # 一行一行写。

坑 2:注释里写了中文,文件头没加编码声明

Python 3 默认 UTF-8,一般不需要加。但如果你还在维护 Python 2 的代码:

# -*- coding: utf-8 -*-
# 这是 Python 2 的写法,Python 3 不需要

坑 3:注释里塞了敏感信息

# 数据库密码:root/admin123
# 不要在代码里这样写——别人一拉代码就看到了

# ✓ 用环境变量
db_password = os.getenv("DB_PASSWORD")

坑 4:注释太长,超过了行宽限制

PEP 8 建议一行代码不超过 79 个字符(注释 72 个字符)。虽然现在显示器很宽,但太长的一行在 Git diff 和分屏时体验很差。

# ✗ 超长注释——diff 时滚来滚去看
# 这段逻辑是从旧版 API 迁移过来的时候改的,因为新版 API 数据结构不一样,里面有个嵌套字段移到了别的位置

# ✓ 分行写
# 这段逻辑是从旧版 API 迁移过来时改的
# 原因:新版 API 数据结构有变化
# 嵌套字段从 result.data.detail 移到了 result.meta.detail

坑 5:以为类型注解会强制类型检查

def add(a: int, b: int) -> int:
    return a + b

# Python 不会阻止你传字符串——它只是个"注解",不强制
result = add("hello", "world")  # 不会报错,返回 "helloworld"

类型注解是给人看、给 IDE 提示、给 mypy 检查的——Python 解释器本身不管。


十、总结

Python 注释的五种形态:

Python 注释

单行注释
# 以井号开头

多行注释
''' 或 ''' 三引号

Docstring
函数/类的三引号说明

类型注解
: int / -> str

标注注释
TODO / FIXME / HACK

速查表:

类型 语法 什么时候用
单行注释 # 注释内容 日常解释代码逻辑
多行注释 ''' 注释 ''' 多行说明(不推荐函数内用)
多行注释(推荐) 每行 # 函数内多行说明
Docstring """ 说明 """ 函数 / 类 / 模块说明
类型注解 name: str 标注参数和返回值类型
标注 # TODO: 标注待办任务
标注 # FIXME: 标注已知 Bug

核心源码文件(参考):

文件 路径
注释示例 2.1注释.py
函数定义 5.3 自定义函数.py

一句话总结: 注释的目的是帮别人(包括三个月后的自己)快速理解代码。写好注释的要点就三条——解释"为什么"而不是"是什么"、代码改了注释跟着改、复杂的逻辑不注释过不了 Code Review。

更多推荐