MySQLdb 脚本连 nowasp、cursor 拿游标执行 select、fetchall 取回元组——这几行在 Python 2 的机器上跑得挺顺,换到 Python 3,最后那句print record[0]直接报错。要把它规范化,先把执行工具接上:TaoToken 的接法不复杂,打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册账号、建一把 Key,再把 Base URL 填 https://taotoken.net/api,模型 ID 以模型广场当时的列表为准。通道通了之后,把原始脚本整段贴给 Codex,让它逐行对照cursor.execute里的 SQL 和fetchall返回的元组顺序,先补上 try/except/finally 保证cursor.close()与db.close()一定执行,再给出两版改法。
这里有个边界要先说清楚:Codex 只负责生成、解释、对照代码,它不会连上你的 nowasp 库,也不会替你执行execute。真正跑 SQL 的是你本机的 Python 解释器,跑完把输出或报错贴回对话,它接着改。数据库那一层始终握在你自己手里,TaoToken 只出现在 Key 和模型通道这一层,这是本文所有步骤的前提。
1. 那段 nowasp 连库脚本,问题不在八行本身
1.1 八行代码各自在干什么
原始脚本的结构很清晰:MySQLdb.connect(host='localhost', user='root', passwd='...', db='nowasp')建立连接,db.cursor()拿一个游标对象,cursor.execute("select * from accounts")把 SQL 送到服务端,cursor.fetchall()把结果集一次性取回内存,最后用一个 for 循环把每条记录的 0 到 3 号下标打印出来。
在 Python 2 里这八行能出结果,因为那时候print还是语句,print record[0], record[1]这种写法合法。到了 Python 3,print变成函数,缺少括号就是SyntaxError: Missing parentheses in call to 'print',脚本连第一行连接代码都跑不到就退出了。
真正麻烦的不是这个语法错,而是它掩盖了另外三个问题:凭据明文写在源码里、游标和连接没有可靠的关闭路径、record[0]到record[3]这种下标取法完全依赖select *的列顺序。语法一改就跑,但后三个问题会在换机器、加字段、并发跑的时候一起爆发。
1.2 把原始写法拆成一张对照表
规范化之前,先看清楚每一行欠了什么。下面这张表不是让你背,而是给 Codex 的提示词当素材——把表里的信息写进提示词,它给出的改法会精准很多。
| 原始写法 | 埋下的问题 | 规范化落点 |
|---|---|---|
MySQLdb.connect(..., passwd='...', db='nowasp') | 密码写死在源码,进版本库就泄露 | 挪到环境变量或本地配置文件,源码里只留os.getenv |
db.cursor() | 游标创建后没有关闭路径,异常时泄漏 | try/finally,或用contextlib.closing |
cursor.execute("select * from accounts") | 列顺序即索引顺序,加字段就错位 | 显式写列名,或改成参数化查询 |
for record in result: print record[0], ... | Python 3 语法错,且下标含义不直观 | 改print(...),或直接元组解包 |
| 没有 try/except | 连接失败时后续代码继续跑,报错信息不好读 | 捕获MySQLdb.Error,打印可读信息 |
表里最容易被忽略的是第三行。fetchall()返回的是元组的元组,外层每个元素对应一行,内层按 SELECT 的列顺序排列。你写record[0]的时候其实是在说"第一列",可代码里看不出第一列是 id 还是 account。等哪天有人给 accounts 表加了一个字段并放在最前面,这四行打印全部错位,而且不会报错——这是比语法错更难查的那种。
2. 给 Codex 接上 TaoToken 通道:config.toml 里只填 /api
2.1 建 Key 这一步,只在官网完成
打开 TaoToken 注册账号,进控制台创建一把 API Key,复制出来先存好,后文统一写成占位符YOUR_API_KEY。顺手在模型广场确认一下你要用的模型 ID,别凭记忆填一个自己以为存在的名字——模型 ID 以 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 上的模型广场列表为准。
有两个反直觉的点值得提前知道。第一,Key 不要写进 Python 脚本,也不要写进config.toml明文里,用环境变量传。第二,官网地址和接口地址是两个东西:注册、建 Key、看模型广场、看用量用带参数的官网链接;填进工具的 Base URL 只能是https://taotoken.net/api,末尾不要补/v1,更不要把官网首页地址填进去。
2.2 ~/.codex/config.toml 里的 model_provider 与 base_url
Codex 的模型通道写在配置文件里,Linux 和 macOS 是~/.codex/config.toml,Windows 是%USERPROFILE%\.codex\config.toml。如果你之前已经在用别的供应商,不要删掉原来的段,新增一个 provider 段即可,切换时改model_provider一行。
# ~/.codex/config.toml model = "YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"base_url就写到/api为止,多一个字符都可能让请求打到不存在的路径上。env_key这一行告诉 Codex 去哪里读密钥,所以还要在终端里把它设上:
# Linux / macOS,写进 ~/.bashrc 或 ~/.zshrc 后重新开一个终端 export TAOTOKEN_API_KEY=YOUR_API_KEY# Windows PowerShell setx TAOTOKEN_API_KEY "YOUR_API_KEY"wire_api的值跟着模型广场的说明走:走 chat 协议的模型填chat,如果该模型标注的是 responses 协议就改成responses。这一点不用猜,模型广场页面会写。
2.3 怎么确认通道真的通了
配置写完先别急着改数据库脚本,花一分钟验证通道。在任意一个项目目录下启动 Codex,发一句和数据库无关的话,比如"用一句话说明 cursor.fetchall() 返回的数据结构"。
能正常回话,说明 Key、base_url、模型 ID 三件事至少对上了,可以进入下一步。如果这里就不通,先解决通道问题再去改代码,否则你会分不清是配置错还是 Python 错——同时排查两个变量是最浪费时间的做法。
3. 把脚本整段贴给 Codex:让它对齐 cursor.execute 与 fetchall
3.1 提示词要写清"只做对照和改写"
很多人贴代码过去只写一句"帮我改一下",结果拿回来的是一份风格完全不同、还引入了新依赖的重写版。要让 Codex 逐行对照,提示词里得把它限制住。下面这段可以直接改一改就用:
下面是一段最小可用的 Python 连库脚本,Python 2 语法,连的是我本机的 localhost / nowasp 库。 要求: 1. 先逐行标注每行在做什么,特别说明 cursor.execute 里的 SQL 与 cursor.fetchall() 返回的元组顺序之间的对应关系。 2. 不要连接任何数据库,也不要执行任何 SQL,只生成和解释代码。 3. 给出两版改法: A 版继续用 MySQLdb,只修 print 语法和异常处理,风格尽量贴近原文; B 版把连接参数拆成配置项,用参数化查询,并保证游标和连接一定关闭。 4. 两版都要保留 for 循环逐条输出的行为,不要改成返回 DataFrame。 脚本如下: (把原始八行粘贴在这里)第 2 条必须写。不写清楚,有些模型会自作主张给你一段"顺手连一下验证"的代码,那是你不想要也不需要的。
3.2 让它先标注 record[0] 到 record[3] 对应哪几列
要让 Codex 说清record[0]到record[3]的含义,得先给它表结构,否则它只能根据列名猜。在你本机的 MySQL 客户端里执行一句:
SHOW COLUMNS FROM accounts;把返回的字段名和顺序贴进对话,再补一句"select *的列顺序就是上面这个顺序,请据此说明record[0]到record[3]分别是什么"。这样它给出的 B 版就能写成显式列名,而不是继续猜。
这里也要重复一次边界:SHOW COLUMNS是你在本地客户端执行的,Codex 不参与执行,它只是读你贴过去的结果。
3.3 try/except/finally 的位置比异常类型更重要
让 Codex 加异常处理的常见结果是:它给MySQLdb.connect包了 try,却没管cursor,或者把db.close()写在 try 的最后一行——一旦中间抛异常,这行永远不会执行,连接就被挂在那里。
正确的结构是三层:连接和游标在 try 之前先初始化为None,try 里只放真正会出错的操作,except 里捕获MySQLdb.Error并打印,finally 里判断非None再关闭,且游标的close()要排在连接的close()前面。在提示词里直接点明这个顺序,能省掉一轮返工。
4. 两版改法:MySQLdb 最小修复版与配置项 + 参数化查询版
4.1 A 版:继续用 MySQLdb,只修 print 与异常
A 版的目标是让这段脚本今天就能跑起来,改动范围压到最小,不引入新依赖,也不改文件结构。
#!/usr/bin/env python # -*- coding: utf-8 -*- import MySQLdb db = None cursor = None try: db = MySQLdb.connect( host="localhost", user="root", passwd="YOUR_DB_PASSWORD", db="nowasp", charset="utf8mb4", ) cursor = db.cursor() cursor.execute("select * from accounts") result = cursor.fetchall() for record in result: print(record[0], record[1], record[2], record[3]) except MySQLdb.Error as e: print("查询失败:", e) finally: if cursor is not None: cursor.close() if db is not None: db.close()改动集中在四处:print加括号;连接和游标提前初始化为None;整段操作进 try;关闭动作进 finally 并且先游标后连接。SQL 一个字没动,select *也保留着,因为它这一版的任务只是"先能跑"。
4.2 B 版:配置项 + 参数化查询 + closing
B 版解决的是"下次还要改"的问题。连接参数从源码里挪出去,游标和连接交给contextlib.closing托管,查询条件用参数化传递。
#!/usr/bin/env python # -*- coding: utf-8 -*- import os from contextlib import closing import MySQLdb DB_CONFIG = { "host": os.getenv("NOWASP_HOST", "localhost"), "user": os.getenv("NOWASP_USER", "root"), "passwd": os.getenv("NOWASP_PASSWORD", ""), "db": os.getenv("NOWASP_DB", "nowasp"), "charset": "utf8mb4", } # 列名以你本地 SHOW COLUMNS FROM accounts 的结果为准 SQL = "select id, account, name, status from accounts where status = %s" def fetch_accounts(status="active"): with closing(MySQLdb.connect(**DB_CONFIG)) as db: with closing(db.cursor()) as cursor: cursor.execute(SQL, (status,)) return cursor.fetchall() def main(): try: rows = fetch_accounts() except MySQLdb.Error as e: print("查询失败:", e) return for account_id, account, name, status in rows: print(account_id, account, name, status) if __name__ == "__main__": main()密码从NOWASP_PASSWORD环境变量读,源码里只留键名。select *换成了显式列名,for循环改成元组解包,record[0]这种下标彻底消失。closing()保证无论正常返回还是中途抛异常,游标和连接都会被关掉——它比 finally 更省心的地方是,你不用再关心关闭顺序。
需要注意的是,B 版只做查询,不涉及写操作。如果后面要加 INSERT/UPDATE,closing()不会替你 commit,得在with块内显式调用db.commit(),这一点可以在对话里继续追问 Codex,让它给你补一段。
4.3 两版怎么选,看你要解决的是哪个问题
| 对比项 | A 版 | B 版 |
|---|---|---|
| 改动范围 | 只动 print 和异常结构 | 连接、SQL、循环全部重排 |
| 依赖 | 仍然只用 MySQLdb | 仍然只用 MySQLdb,加标准库 |
| 凭据位置 | 仍在源码里 | 挪到环境变量 |
| 列顺序依赖 | 仍靠select * | 显式列名 + 元组解包 |
| 适合场景 | 老脚本临时救急、要跑一次 | 后面还要维护、要进版本库 |
实践里的顺序通常是先落 A 版确认逻辑没跑偏,隔一两天再落 B 版。两版并存不冲突,A 版可以留在本地当对照。
5. 本地跑通再回传报错:print record[0] 之外的几个高频坑
5.1 两版都在本机跑一遍
A 版和 B 版都存成.py文件,在终端里执行,观察输出是不是四列值。B 版跑之前记得把环境变量设好:
export NOWASP_HOST=localhost export NOWASP_USER=root export NOWASP_PASSWORD=YOUR_DB_PASSWORD export NOWASP_DB=nowasp python check_accounts.pyA 版要跑,得先把本地 MySQL 的 root 密码替换掉YOUR_DB_PASSWORD。这一步没人能替你做,因为数据库在你的机器上。
5.2 五个报错,各自对应哪一行
改完之后最常见的几个报错,基本都能一眼定位:
SyntaxError: Missing parentheses in call to 'print':还在用 Python 2 的 print 语句,看 for 循环那一行。ImportError: No module named 'MySQLdb':MySQLdb 对应的安装包是 mysqlclient,本机可能没装,或者缺少 MySQL 的开发头文件;实在装不上,把import MySQLdb换成import pymysql as MySQLdb也能先跑,但异常类型要跟着换成pymysql.Error。OperationalError: (1045, "Access denied for user 'root'@'localhost'"):账号密码不对,检查环境变量有没有真的生效。OperationalError: (1049, "Unknown database 'nowasp'"):库名写错,或者连到了另一台 MySQL 实例。- 中文显示成问号或乱码:连接参数里补
charset="utf8mb4",A 版和 B 版都已经加上了。
Codex 那边如果连不上,先看两件事:Key 有没有正确写进TAOTOKEN_API_KEY,base_url是不是严格写成https://taotoken.net/api。写成官网首页地址、或者手滑在后面补了/v1,都会直接失败。
5.3 把输出和报错贴回去,让它接着改
这一步才是 Codex 真正省时间的地方。把完整的 traceback 连同脚本一起贴回去,问一句"这个报错落在第几行,两版分别怎么改",比只贴报错最后一行有效得多。它能看到你的环境变量名、SQL 写法、游标关闭顺序,给出的修改建议才有针对性。
如果错误来自数据库本身(比如权限、库不存在),那部分是你在 MySQL 客户端里排查完、改了连接参数之后再回到 Codex。工具链分工清楚,来回折腾的次数会少很多。
6. 改完这个脚本,顺手把通道和 Key 对上
6.1 用同一把 Key 在模型对话里试一次
脚本改完、Codex 通道也通了,可以用同一把YOUR_API_KEY去 TaoToken 模型对话 发一条测试消息,比如让它解释fetchall()和fetchone()的区别。这一步有两个作用:确认这把 Key 在网页侧也能用,顺便看看刚才那些请求有没有记到账上。如果打算长期让 Codex 跟着改脚本,可以到 Coding Plan 看一眼套餐够不够用;要再建一把给别的项目用,在 控制台 API Keys 里创建即可。
6.2 三个入口,按需打开
通道配好之后,日常会用到三个页面:建 Key 和查用量去 https://taotoken.net/?utm_source=taotoken_aicg_blog_end ;调试提示词去模型对话;如果后面想把这套通道迁到别的命令行工具上,环境变量的字段名和写法可以参考 Claude Code 接入文档 ,里面的ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN、ANTHROPIC_MODEL三个变量和本篇 Codex 的base_url、env_key、model是一一对应的思路——Base URL 都填https://taotoken.net/api,末尾不加/v1。
最后留一句提醒:select *改成显式列名这件事,别一次改完所有脚本。挑一个天天要跑的,跑稳一周再动下一个。数据库脚本的坑大多不是语法错,而是"看起来跑通了、结果列却悄悄换了位置",慢一点反而更快。