用 AI 辅助写 Python——10倍速开发方法论
第3章:用 AI 辅助写 Python——10倍速开发方法论
AI 编程助手已经足够强大,但大多数人用错了方式。他们要么让 AI 一口气写完所有代码(然后发现根本跑不起来),要么只是用 AI 搜索替代 Google,浪费了最大的价值。本章给你一套经过验证的、真正能提升 10 倍开发速度的方法论——不是让 AI 替代你思考,而是让你和 AI 高效配合。
AI 辅助编程的正确姿势
为什么不能让 AI 一次写完所有代码
新手最常见的错误是把需求整个扔给 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 里明确说明:
- **Python 版本:**Python 3.11 还是 3.9?某些语法和库只在特定版本可用。
- **已有依赖:**你的
requirements.txt里已经有什么库?避免 AI 引入你不需要的依赖。 - **目标环境:**是本地跑、云服务器、还是 Docker 容器?有没有特殊限制(如无网络访问、内存限制)?
- **相关代码片段:**如果要扩展现有代码,把相关的函数签名或类定义粘贴给 AI。
要素二:要求类型提示和 docstring
默认情况下 AI 不一定会加类型提示(Type Hints)和文档字符串(docstring)。明确要求:
❌ 不带约束的 Prompt
帮我写一个函数,读取 CSV 文件,返回按销售额降序排列的前10行 **问题:**AI 会给你一个能运行的函数,但没有类型提示、没有 docstring、没有错误处理,代码可维护性差。
✅ 带约束的专业 Prompt
Python 3.11 环境,已有依赖:pandas 2.x。
帮我写一个函数,读取 CSV 文件,返回按销售额降序排列的前 N 行。
要求:
- 完整的类型提示(参数和返回值)
- Google 风格的 docstring,说明参数含义和返回值类型
- 异常处理:文件不存在、列名不存在的情况
- 解释关键设计决策(为什么这样写,而不是用 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 [版本],依赖:[库列表]。
要求:
- 类型提示 + docstring
- 处理 [列举关键异常场景]
- 解释:为什么选用 [库A] 而不是 [库B]?关键算法或模式的选择理由是什么?
Prompt 对比:从模糊到精确
场景:异步爬虫 你需要写一个能异步抓取多个 URL 的爬虫,带反爬处理,结果存为 dataclass。
#### ❌ 模糊 Prompt
写一个爬虫
AI 不知道你要同步还是异步,用哪个库,有没有反爬,结果怎么存。给出的代码大概率是最基础的 requests + for 循环,不符合任何实际需求。
#### ✅ 精确 Prompt
Python 3.11,使用 httpx(异步客户端)+ BeautifulSoup4。
帮我写一个异步 URL 抓取器:
-
接收 URL 列表,并发抓取(最大并发 10)
-
处理反爬:随机 User-Agent、请求间随机延迟 0.5~2s、遇 429 自动退避重试
-
提取页面标题和正文纯文本
-
结果存为 dataclass(字段:url, title, body, status_code, fetched_at)
-
类型提示 + docstring + 异常处理
-
解释为什么用 httpx 而不是 aiohttp
🤖 AI 生成代码(关键结构)
import asyncio import random from dataclasses import dataclass, field from datetime import datetime, timezone
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
【预期行为】 data 里的字符串是价格,我期望 calculate_total 返回 51.25。
请告诉我:1. 为什么报错;2. 如何修复;3. 如何预防类似问题(类型校验或类型转换最佳实践)。 给出了完整 Traceback、最小复现代码、数据来源说明(从 CSV 读入,所以是字符串)和预期行为。AI 能精准指出问题并给出防御性的修复方案。
分析复杂 Traceback 的技巧
Python 的 Traceback 是从上到下按调用顺序排列的,最后一行才是真正的错误位置。但真正的根因往往不在最后一行,而在调用栈的中间某处。
遇到多层嵌套的 Traceback,可以用这个 Prompt 模板:
✅ 分析复杂 Traceback 的 Prompt
帮我分析这个 Traceback,我需要你:
- 用一句话总结这个错误的根本原因
- 指出 Traceback 中哪一帧(frame)是真正的问题所在(通常不是最后一帧)
- 给出修复方案
- 如果是我代码的问题,给出修复后的代码片段
【完整 Traceback】 [粘贴完整 Traceback]
【相关代码】 [粘贴涉及的函数]
代码 Review 流程
让 AI 帮你 Review 代码,是提升代码质量最低成本的方式。关键是要告诉 AI 从哪些维度审查。
Review Prompt 模板
✅ 全面 Review Prompt
请从以下维度 Review 这段 Python 代码:
- 正确性:逻辑是否有 bug?边界情况是否处理?
- 性能:是否有明显的性能瓶颈(大循环内的重复计算、N+1 查询等)?
- 安全性:是否有安全隐患(命令注入、路径遍历、硬编码凭据等)?
- 可维护性:函数是否过长?命名是否清晰?是否需要拆分?
- 测试覆盖:哪些边界情况需要单元测试?请给出测试用例建议。
对每个问题,给出:问题描述 + 具体代码位置 + 修复建议(如果有更好的写法,直接给出)。
【待 Review 代码】 [粘贴代码]
让 AI 生成单元测试
写单元测试往往是开发者最不愿意做的事情。AI 在这方面非常擅长,而且生成的测试质量相当高:
✅ 生成单元测试的 Prompt
为下面这个函数用 pytest 生成完整的单元测试。
要求:
- 覆盖正常路径、边界情况(空输入、最大/最小值)、异常路径(非法参数)
- 使用 pytest.mark.parametrize 对多组输入做参数化测试
- 每个测试函数加上中文注释说明测试意图
- 如果函数涉及文件/网络/数据库,用 pytest 的 monkeypatch 或 mock 隔离
【函数代码】 [粘贴函数]
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 生态的团队。不要只用一个工具——不同工具对同一问题的视角往往互补。
何时用哪个工具
- **写新功能/模块:**Cursor,它能看到你的整个项目,生成的代码和现有代码风格一致。
- **调试复杂问题:**Claude,粘贴完整 Traceback 和相关代码,它的推理能力最强。
- **重复性代码(CRUD、配置、测试):**Copilot,行级补全速度最快。
- **从 UI 截图/设计稿生成代码:**GPT-4o,多模态优势明显。
- **学习一个新库/概念:**Claude 或 GPT-4o,要求它"一步步解释"和"举例说明"。
实战案例:用 AI 30分钟写一个文件整理脚本
下面是一个完整的 AI 协作开发记录。目标:写一个脚本,自动把下载文件夹里的文件按类型分类到子文件夹(图片、文档、视频、压缩包等)。
第一轮对话:需求分解与骨架生成
✅ 第一轮 Prompt(需求与骨架)
Python 3.11,只用标准库(pathlib、shutil)。
我要写一个文件整理脚本:扫描指定目录,把文件按扩展名分类移动到子文件夹。
分类规则:
- 图片:.jpg .jpeg .png .gif .webp .svg - 文档:.pdf .docx .xlsx .pptx .txt .md - 视频:.mp4 .mov .avi .mkv - 音频:.mp3 .wav .flac .aac - 压缩包:.zip .tar .gz .7z .rar - 其他: 要求:
- 类型提示 + docstring
- 跳过已经在子文件夹中的文件(避免重复处理)
- 同名文件自动加序号(不覆盖)
- 干运行模式(--dry-run,只打印操作,不实际移动)
- 给出主函数骨架,细节我来填
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 我整个脚本的安全性和健壮性:
- 有没有可能误删或误覆盖文件?
- 在 Windows/macOS/Linux 三个平台上有没有兼容性问题?
- 如果整理中途被中断(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章:异常处理与日志