第 3 章

用 AI 辅助写 Python——10倍速开发方法论

第3章:用 AI 辅助写 Python——10倍速开发方法论

AI 编程助手已经足够强大,但大多数人用错了方式。他们要么让 AI 一口气写完所有代码(然后发现根本跑不起来),要么只是用 AI 搜索替代 Google,浪费了最大的价值。本章给你一套经过验证的、真正能提升 10 倍开发速度的方法论——不是让 AI 替代你思考,而是让你和 AI 高效配合。

AI 辅助编程的正确姿势

为什么不能让 AI 一次写完所有代码

新手最常见的错误是把需求整个扔给 AI,比如"帮我写一个爬虫,能爬取京东商品价格,存到数据库,每天自动运行"。AI 会生成几百行代码,但这段代码有几个致命问题:

正确的协作流程

需求分解 AI 生成骨架 自己填充业务逻辑 AI Review 测试上线

**第一步:需求分解。**在和 AI 对话之前,先自己想清楚:这个脚本要做什么?输入是什么?输出是什么?有哪些边界情况?把大任务拆成小模块,每次只让 AI 解决一个具体问题。

**第二步:AI 生成骨架。**带着你分解好的需求,让 AI 生成代码框架——函数签名、类结构、主流程,但不要求 AI 填满所有细节。骨架代码短、可读、你能理解。

**第三步:自己填充业务逻辑。**骨架里那些只有你知道的部分——你的数据库连接字符串、你公司的特殊业务规则、你服务器的路径配置——由你自己填进去。这一步让你真正理解代码,也让代码真正符合你的环境。

**第四步:AI Review。**代码写好之后,再次请 AI 帮你审查:有没有潜在的性能问题?有没有安全隐患?异常处理完整吗?这一步往往能发现你自己发现不了的问题。

**核心原则:**你是架构师,AI 是你的高级工程师助手。你决定做什么、怎么组织,AI 帮你快速实现和优化细节。不要倒过来。

Prompt 工程 for Python(专业向)

写出好的 Prompt 是和 AI 高效合作的核心技能。对于 Python 开发,好的 Prompt 有几个固定要素。

要素一:提供上下文

AI 不知道你的项目环境。在 Prompt 里明确说明:

要素二:要求类型提示和 docstring

默认情况下 AI 不一定会加类型提示(Type Hints)和文档字符串(docstring)。明确要求:

❌ 不带约束的 Prompt

帮我写一个函数,读取 CSV 文件,返回按销售额降序排列的前10行 **问题:**AI 会给你一个能运行的函数,但没有类型提示、没有 docstring、没有错误处理,代码可维护性差。

✅ 带约束的专业 Prompt

Python 3.11 环境,已有依赖:pandas 2.x。

帮我写一个函数,读取 CSV 文件,返回按销售额降序排列的前 N 行。

要求:

  1. 完整的类型提示(参数和返回值)
  2. Google 风格的 docstring,说明参数含义和返回值类型
  3. 异常处理:文件不存在、列名不存在的情况
  4. 解释关键设计决策(为什么这样写,而不是用 sorted()) **改进:**明确版本和依赖,要求类型提示和 docstring,指定了两个关键异常场景,还要求 AI 解释设计决策。得到的代码质量远高于前者。

遵循这个模板,AI 会生成如下质量的代码:

import pandas as pd
from pathlib import Path

def top_n_by_sales(
    csv_path: str | Path,
    sales_column: str = "sales",
    n: int = 10,
) -> pd.DataFrame:
    """读取 CSV 文件并返回销售额最高的前 N 行。

    Args:
        csv_path: CSV 文件路径,支持字符串或 Path 对象。
        sales_column: 用于排序的销售额列名,默认为 "sales"。
        n: 返回的行数,默认为 10。

    Returns:
        按 sales_column 降序排列的前 n 行 DataFrame。

    Raises:
        FileNotFoundError: 文件路径不存在时抛出。
        KeyError: CSV 中不包含指定的 sales_column 列时抛出。
    """
    path = Path(csv_path)
    if not path.exists():
        raise FileNotFoundError(f"文件不存在:{path}")

    df = pd.read_csv(path)

    if sales_column not in df.columns:
        raise KeyError(
            f"列 '{sales_column}' 不存在。"
            f"可用列:{list(df.columns)}"
        )

    # 使用 nlargest 而非 sort_values().head():
    # nlargest 内部使用堆排序,时间复杂度 O(n log k),
    # 在数据量大、k 远小于 n 时显著优于全排序 O(n log n)。
    return df.nlargest(n, sales_column)

要素三:要求 AI 解释每个关键决策

在 Prompt 末尾加上"请解释关键设计决策"或"为什么选择这个方法而不是 X",你会得到附带解释的代码。这比单独问"为什么这样写"更高效,也帮助你真正理解代码,而不只是复制粘贴。

✅ 带解释要求的 Prompt 模板

帮我实现 [功能描述]。

环境:Python [版本],依赖:[库列表]。

要求:

Prompt 对比:从模糊到精确

场景:异步爬虫 你需要写一个能异步抓取多个 URL 的爬虫,带反爬处理,结果存为 dataclass。

#### ❌ 模糊 Prompt
写一个爬虫
AI 不知道你要同步还是异步,用哪个库,有没有反爬,结果怎么存。给出的代码大概率是最基础的 requests + for 循环,不符合任何实际需求。

#### ✅ 精确 Prompt
Python 3.11,使用 httpx(异步客户端)+ BeautifulSoup4。

帮我写一个异步 URL 抓取器:

import httpx from bs4 import BeautifulSoup **AI 会同时解释:**httpx 原生支持 async/await 且 API 与 requests 高度一致,aiohttp API 更底层、学习成本高;对于这类脚本场景,httpx 是更务实的选择。这种解释帮助你理解技术选型,而不只是被动接受代码。

让 AI 帮你调试

调试是 Python 学习最痛苦的环节。好消息是,AI 在分析 Traceback 上非常擅长——但前提是你给它足够的信息。

调试 Prompt 的黄金原则

**原则一:完整错误信息,不要截断。**把完整的 Traceback 复制给 AI,包括最后那一行具体的错误类型和消息。很多人只复制最后一行,丢失了调用栈信息,AI 无法判断错误发生在哪个函数里。

**原则二:提供最小复现代码(MRE)。**不要把整个 500 行的文件扔给 AI。先把能复现问题的最小代码片段提取出来——通常是 10~30 行。这样 AI 能精确定位问题,你也能更快理解答案。

**原则三:说明你的预期行为。**告诉 AI"我期望这段代码做 X,但实际上它做了 Y"。否则 AI 只知道有错,不知道你的目标是什么。

调试 Prompt 标准模板 Python 脚本运行报错,需要 AI 帮助定位问题。

#### ❌ 无效调试 Prompt
我的代码报错了,怎么回事?

TypeError: unsupported operand type(s) for +: 'int' and 'str' AI 只知道错误类型,不知道在哪行代码,不知道变量的值,不知道你想做什么。

#### ✅ 有效调试 Prompt
Python 3.11,运行以下代码时报错:

【最小复现代码】 def calculate_total(prices: list) -> float: total = 0 for price in prices: total = total + price # 报错在这行 return total

data = ["12.5", "30.0", "8.75"] # 从 CSV 读入的数据 print(calculate_total(data))

【完整 Traceback】 Traceback (most recent call last): File "main.py", line 9, in print(calculate_total(data)) File "main.py", line 4, in calculate_total total = total + price TypeError: unsupported operand type(s) for +: 'int' and 'str'

【预期行为】 data 里的字符串是价格,我期望 calculate_total 返回 51.25。

请告诉我:1. 为什么报错;2. 如何修复;3. 如何预防类似问题(类型校验或类型转换最佳实践)。 给出了完整 Traceback、最小复现代码、数据来源说明(从 CSV 读入,所以是字符串)和预期行为。AI 能精准指出问题并给出防御性的修复方案。

分析复杂 Traceback 的技巧

Python 的 Traceback 是从上到下按调用顺序排列的,最后一行才是真正的错误位置。但真正的根因往往不在最后一行,而在调用栈的中间某处。

遇到多层嵌套的 Traceback,可以用这个 Prompt 模板:

✅ 分析复杂 Traceback 的 Prompt

帮我分析这个 Traceback,我需要你:

  1. 用一句话总结这个错误的根本原因
  2. 指出 Traceback 中哪一帧(frame)是真正的问题所在(通常不是最后一帧)
  3. 给出修复方案
  4. 如果是我代码的问题,给出修复后的代码片段

【完整 Traceback】 [粘贴完整 Traceback]

【相关代码】 [粘贴涉及的函数]

代码 Review 流程

让 AI 帮你 Review 代码,是提升代码质量最低成本的方式。关键是要告诉 AI 从哪些维度审查。

Review Prompt 模板

✅ 全面 Review Prompt

请从以下维度 Review 这段 Python 代码:

  1. 正确性:逻辑是否有 bug?边界情况是否处理?
  2. 性能:是否有明显的性能瓶颈(大循环内的重复计算、N+1 查询等)?
  3. 安全性:是否有安全隐患(命令注入、路径遍历、硬编码凭据等)?
  4. 可维护性:函数是否过长?命名是否清晰?是否需要拆分?
  5. 测试覆盖:哪些边界情况需要单元测试?请给出测试用例建议。

对每个问题,给出:问题描述 + 具体代码位置 + 修复建议(如果有更好的写法,直接给出)。

【待 Review 代码】 [粘贴代码]

让 AI 生成单元测试

写单元测试往往是开发者最不愿意做的事情。AI 在这方面非常擅长,而且生成的测试质量相当高:

✅ 生成单元测试的 Prompt

为下面这个函数用 pytest 生成完整的单元测试。

要求:

【函数代码】 [粘贴函数]

import pytest
from pathlib import Path
from unittest.mock import patch, mock_open

# 测试 top_n_by_sales 函数
class TestTopNBySales:

    @pytest.mark.parametrize("n,expected_len", [
        (3, 3),   # 正常:请求 3 条,返回 3 条
        (10, 5),  # 边界:请求比总行数多,返回全部
        (1, 1),   # 边界:只要最高一条
    ])
    def test_normal_cases(self, tmp_path, n, expected_len):
        """测试正常场景:返回正确的行数,且按销售额降序"""
        csv_content = "product,sales\nA,100\nB,50\nC,200\nD,80\nE,30\n"
        csv_file = tmp_path / "test.csv"
        csv_file.write_text(csv_content)

        result = top_n_by_sales(csv_file, n=n)
        assert len(result) == expected_len
        # 验证降序
        assert result["sales"].is_monotonic_decreasing

    def test_file_not_found(self):
        """测试文件不存在时抛出 FileNotFoundError"""
        with pytest.raises(FileNotFoundError, match="文件不存在"):
            top_n_by_sales("/nonexistent/path.csv")

    def test_column_not_found(self, tmp_path):
        """测试列名不存在时抛出 KeyError"""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("product,revenue\nA,100\n")
        with pytest.raises(KeyError, match="sales"):
            top_n_by_sales(csv_file, sales_column="sales")

AI 工具对比:选对工具事半功倍

市面上的 AI 编程工具各有侧重,了解它们的定位能帮你在不同场景选择最合适的工具。

工具 核心定位 最适合的场景 Python 自动化适配度
Cursor AI 原生 IDE,深度集成代码库上下文 大型项目开发;需要跨文件理解和修改;重构 ★★★★★ 理解整个项目结构,可以跨文件修改代码
GitHub Copilot VSCode/JetBrains 插件,行级补全 日常编码加速;函数级补全;已有代码库的扩展 ★★★★☆ 补全准确度高,但上下文窗口有限
Claude 对话式 AI,长上下文,深度推理 复杂架构设计;代码 Review;调试分析;解释原理 ★★★★★ 最擅长解释"为什么",适合学习和复杂问题
GPT-4o 通用多模态 AI,支持图片输入 截图/UI 转代码;多模态任务;快速原型 ★★★★☆ 速度快,截图识别后生成代码方便

**推荐组合:**日常开发用 Cursor(IDE 级集成)+ Claude(复杂问题和 Review)。Copilot 适合已经深度绑定 VSCode 生态的团队。不要只用一个工具——不同工具对同一问题的视角往往互补。

何时用哪个工具

实战案例:用 AI 30分钟写一个文件整理脚本

下面是一个完整的 AI 协作开发记录。目标:写一个脚本,自动把下载文件夹里的文件按类型分类到子文件夹(图片、文档、视频、压缩包等)。

第一轮对话:需求分解与骨架生成

✅ 第一轮 Prompt(需求与骨架)

Python 3.11,只用标准库(pathlib、shutil)。

我要写一个文件整理脚本:扫描指定目录,把文件按扩展名分类移动到子文件夹。

分类规则:

AI 给出了以下骨架(精简展示):

from pathlib import Path
import shutil
import argparse
from typing import Final

# 扩展名 CATEGORY_MAP: Final[dict[str, str]] = {
    ".jpg": "Images", ".jpeg": "Images", ".png": "Images",
    ".gif": "Images", ".webp": "Images", ".svg": "Images",
    ".pdf": "Documents", ".docx": "Documents", ".xlsx": "Documents",
    ".pptx": "Documents", ".txt": "Documents", ".md": "Documents",
    ".mp4": "Videos", ".mov": "Videos", ".avi": "Videos", ".mkv": "Videos",
    ".mp3": "Audio", ".wav": "Audio", ".flac": "Audio", ".aac": "Audio",
    ".zip": "Archives", ".tar": "Archives", ".gz": "Archives",
    ".7z": "Archives", ".rar": "Archives",
}

def get_destination(file: Path, target_dir: Path) -> Path:
    """根据文件扩展名确定目标路径,处理同名冲突。"""
    ...  # 待实现

def organize_directory(source: Path, dry_run: bool = False) -> dict[str, int]:
    """整理目录,返回各分类移动数量的统计。"""
    ...  # 待实现

def main() -> None:
    parser = argparse.ArgumentParser(description="文件自动整理工具")
    parser.add_argument("directory", type=Path, help="要整理的目录路径")
    parser.add_argument("--dry-run", action="store_true", help="只打印操作,不实际移动")
    args = parser.parse_args()
    ...  # 待实现

if __name__ == "__main__":
    main()

第二轮对话:实现同名冲突处理

✅ 第二轮 Prompt(具体功能)

帮我实现 get_destination 函数。

同名冲突规则:如果目标位置已有 report.pdf,新文件命名为 report_1.pdf;如果 report_1.pdf 也存在,命名为 report_2.pdf,以此类推。

注意:不要用 while True 无限循环,限制最大尝试次数为 9999,超过时抛出 RuntimeError。

def get_destination(file: Path, target_dir: Path) -> Path:
    """确定文件的目标路径,自动处理同名冲突。

    Args:
        file: 源文件 Path 对象。
        target_dir: 目标分类文件夹 Path 对象。

    Returns:
        不与现有文件冲突的目标 Path。

    Raises:
        RuntimeError: 同名文件超过 9999 个时抛出。
    """
    category = CATEGORY_MAP.get(file.suffix.lower(), "Others")
    dest_folder = target_dir / category
    dest_folder.mkdir(parents=True, exist_ok=True)

    candidate = dest_folder / file.name
    if not candidate.exists():
        return candidate

    stem = file.stem
    suffix = file.suffix
    for i in range(1, 10000):
        candidate = dest_folder / f"{stem}_{i}{suffix}"
        if not candidate.exists():
            return candidate

    raise RuntimeError(f"同名文件已超过 9999 个:{file.name}")

第三轮对话:AI Review

✅ 第三轮 Prompt(Review)

请 Review 我整个脚本的安全性和健壮性:

  1. 有没有可能误删或误覆盖文件?
  2. 在 Windows/macOS/Linux 三个平台上有没有兼容性问题?
  3. 如果整理中途被中断(Ctrl+C),文件状态是否安全?

AI 指出了两个关键问题:① shutil.move 在跨设备(不同磁盘分区)移动时可能失败,需要捕获异常;② 扩展名比较要统一转小写,否则 .JPG.jpg 会被识别为不同类型。

最终完整脚本

"""文件自动整理工具:按类型将文件分类到子文件夹。

使用方法:
    python organize.py ~/Downloads
    python organize.py ~/Downloads --dry-run
"""
from pathlib import Path
import shutil
import argparse
import logging
from typing import Final

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
logger = logging.getLogger(__name__)

CATEGORY_MAP: Final[dict[str, str]] = {
    ".jpg": "Images", ".jpeg": "Images", ".png": "Images",
    ".gif": "Images", ".webp": "Images", ".svg": "Images",
    ".pdf": "Documents", ".docx": "Documents", ".xlsx": "Documents",
    ".pptx": "Documents", ".txt": "Documents", ".md": "Documents",
    ".mp4": "Videos", ".mov": "Videos", ".avi": "Videos", ".mkv": "Videos",
    ".mp3": "Audio", ".wav": "Audio", ".flac": "Audio", ".aac": "Audio",
    ".zip": "Archives", ".tar": "Archives", ".gz": "Archives",
    ".7z": "Archives", ".rar": "Archives",
}

KNOWN_CATEGORIES: Final[set[str]] = set(CATEGORY_MAP.values()) | {"Others"}

def get_destination(file: Path, source_dir: Path) -> Path:
    """确定文件的目标路径,自动处理同名冲突。"""
    category = CATEGORY_MAP.get(file.suffix.lower(), "Others")
    dest_folder = source_dir / category
    dest_folder.mkdir(parents=True, exist_ok=True)

    candidate = dest_folder / file.name
    if not candidate.exists():
        return candidate

    for i in range(1, 10000):
        candidate = dest_folder / f"{file.stem}_{i}{file.suffix}"
        if not candidate.exists():
            return candidate

    raise RuntimeError(f"同名文件已超过 9999 个:{file.name}")

def organize_directory(source: Path, dry_run: bool = False) -> dict[str, int]:
    """整理目录,返回各分类移动数量的统计。

    Args:
        source: 要整理的目录路径。
        dry_run: 若为 True,只打印操作而不实际移动文件。

    Returns:
        各分类名称到已移动文件数量的映射。
    """
    stats: dict[str, int] = {}

    for file in source.iterdir():
        if not file.is_file():
            continue
        # 跳过已在分类子文件夹中的文件
        if file.parent.name in KNOWN_CATEGORIES:
            continue

        dest = get_destination(file, source)
        category = dest.parent.name
        stats[category] = stats.get(category, 0) + 1

        if dry_run:
            logger.info("[DRY-RUN] %s         else:
            try:
                shutil.move(str(file), dest)
                logger.info("已移动:%s             except OSError as exc:
                logger.error("移动失败:%s (%s)", file.name, exc)

    return stats

def main() -> None:
    parser = argparse.ArgumentParser(description="文件自动整理工具")
    parser.add_argument("directory", type=Path, help="要整理的目录路径")
    parser.add_argument("--dry-run", action="store_true", help="预览操作,不实际移动")
    args = parser.parse_args()

    if not args.directory.is_dir():
        logger.error("路径不存在或不是目录:%s", args.directory)
        return

    mode = "(预览模式)" if args.dry_run else ""
    logger.info("开始整理%s:%s", mode, args.directory)
    stats = organize_directory(args.directory, dry_run=args.dry_run)

    logger.info("整理完成:%s", stats)

if __name__ == "__main__":
    main()

**复盘:**整个过程用了 3 轮对话,约 25 分钟。第一轮建立骨架(10 分钟),第二轮实现关键细节(8 分钟),第三轮 Review 并修复问题(7 分钟)。手写同样质量的代码至少需要 2 小时。关键在于:你在掌控整体结构,AI 在帮你快速实现和检查细节。

上一章

下一章
第4章:异常处理与日志
本章评分
4.8  / 5  (74 评分)

💬 留言讨论