这是 Python 系列的收官之战。前面 13 课,每一课你都"学会了一个零件"。这一课,我们要把这些零件组装成一台能转的机器:一个真正能用的命令行小工具。

我们的目标:写一个 待办事项(Todo)管理器。它能把任务存到文件里,随时增、删、查,下次打开程序数据还在。

为什么选"待办"?因为它几乎用遍了前 13 课的所有知识点,代码又不至于太长,是最佳的"毕业作品"。

一、先想清楚:程序要能做什么?

动手写代码前,先想明白功能,这叫"需求分析"。我们的工具要支持 4 个命令:

命令作用
add 买菜添加一条待办
list列出所有待办
done 1把第 1 条标记为完成
del 1删除第 1 条

数据存到一个文本文件里(比如 todo.txt),这样关掉程序数据也不丢——这用第 7 课的文件读写


二、数据怎么存?——第 7 课:文件读写

最简单的存法:一行一条任务。完成了就写个 [x],没完成写 [ ]

[x] 买菜
[ ] 写作业
[ ] 健身

读文件用 with open(...) as f(第 7 课),把每一行读进一个列表:

def 读取任务():
    try:
        with open("todo.txt", "r", encoding="utf-8") as f:
            return [line.strip() for line in f if line.strip()]
    except FileNotFoundError:
        return []          # 文件还不存在,就当没有任务
这里用了第 9 课的 try/except:第一次运行没有 todo.txt,不能让它崩。

写回文件也简单:

def 保存任务(任务列表):
    with open("todo.txt", "w", encoding="utf-8") as f:
        for 任务 in 任务列表:
            f.write(任务 + "\n")

三、命令行参数怎么拿?—— sys.argv

程序要在命令行里接收 add 买菜 这样的输入,靠的是 sys.argv:一个列表,存着"运行命令时的所有参数"。

import sys
print(sys.argv)
# 运行 python todo.py add 买菜
# 输出:['todo.py', 'add', '买菜']
  • sys.argv[0]:脚本名
  • sys.argv[1]:第一个参数(这里是 add
  • sys.argv[2]:第二个参数(这里是 买菜

所以我们的程序主体就是:sys.argv[1] 是什么命令,就干什么——这用第 3 课的 if/elif


四、完整代码:全部串起来

新建一个 todo.py,把下面整段贴进去:

import sys

文件名 = "todo.txt"

def 读取任务():
    """读文件,返回任务列表。文件不存在就返回空列表。"""
    try:
        with open(文件名, "r", encoding="utf-8") as f:
            return [line.strip() for line in f if line.strip()]
    except FileNotFoundError:
        return []

def 保存任务(任务列表):
    """把任务列表写回文件。"""
    with open(文件名, "w", encoding="utf-8") as f:
        for 任务 in 任务列表:
            f.write(任务 + "\n")

def 添加(内容):
    任务列表 = 读取任务()
    任务列表.append(f"[ ] {内容}")       # 新增,默认未完成
    保存任务(任务列表)
    print(f"✓ 已添加:{内容}")

def 列出():
    任务列表 = 读取任务()
    if not 任务列表:
        print("暂无任务,先 add 一个吧~")
        return
    for i, 任务 in enumerate(任务列表, start=1):
        print(f"{i}. {任务}")            # 打印序号 + 任务

def 完成(序号):
    任务列表 = 读取任务()
    if 1 <= 序号 <= len(任务列表):
        # 把 [ ] 换成 [x]
        任务列表[序号 - 1] = 任务列表[序号 - 1].replace("[ ]", "[x]", 1)
        保存任务(任务列表)
        print(f"✓ 已完成:{任务列表[序号 - 1]}")
    else:
        print("✗ 序号超出范围,先 list 看看有几个任务")

def 删除(序号):
    任务列表 = 读取任务()
    if 1 <= 序号 <= len(任务列表):
        被删 = 任务列表.pop(序号 - 1)     # 第 4 课的 pop
        保存任务(任务列表)
        print(f"✓ 已删除:{被删}")
    else:
        print("✗ 序号超出范围,先 list 看看有几个任务")

def main():
    if len(sys.argv) < 2:                 # 没给命令,提示用法
        print("用法:")
        print("  python todo.py add 内容   # 添加")
        print("  python todo.py list       # 列出")
        print("  python todo.py done 序号  # 完成")
        print("  python todo.py del 序号   # 删除")
        return

    命令 = sys.argv[1]

    if 命令 == "add":
        if len(sys.argv) >= 3:
            添加(sys.argv[2])
        else:
            print("✗ add 后面要跟内容,如:python todo.py add 买菜")
    elif 命令 == "list":
        列出()
    elif 命令 in ("done", "del"):
        if len(sys.argv) >= 3 and sys.argv[2].isdigit():
            序号 = int(sys.argv[2])
            if 命令 == "done":
                完成(序号)
            else:
                删除(序号)
        else:
            print("✗ 后面要跟数字序号,如:python todo.py done 1")
    else:
        print(f"✗ 未知命令:{命令},用 list 看看能干什么")

if __name__ == "__main__":
    main()

五、运行你的第一个小工具

在命令行里,切到 todo.py 所在目录,依次试:

python todo.py add 买菜
python todo.py add 写作业
python todo.py list
python todo.py done 1
python todo.py list
python todo.py del 2
python todo.py list

你会看到任务一条条增加、完成、删除,而且关掉程序再打开,数据还在——因为它们已经存进了 todo.txt


六、这份代码用到了哪些课?

复盘一下,你会发现这 100 行代码几乎串起了整个系列:

知识点出处用在哪
变量、字符串、列表第 2、4 课存任务、格式化
if/elif/else第 3 课根据命令分流
for 循环、enumerate第 3 课遍历打印任务
def 函数第 6 课每个功能一个函数
with open 文件读写第 7 课存/读任务
try/except第 9 课处理文件不存在
str.replaceisdigit第 7 课标记完成、校验输入
pop、切片第 4 课删除任务

七、进阶:argparse——命令行参数更专业

第三节用 sys.argv 手写参数判断,能跑,但有几个毛病:参数顺序固定、没有 --help、错误提示要自己一行行写。标准库里的 argparse 就是干这个的专业工具——它自动生成帮助、自动校验参数。

import argparse

parser = argparse.ArgumentParser(description="待办事项管理器")
parser.add_argument("命令", choices=["add", "list", "done", "del"], help="要执行的操作")
parser.add_argument("参数", nargs="?", default="", help="add 的内容,或 done/del 的序号")

args = parser.parse_args()
print("你输入了:", args.命令, args.参数)

几个要点:

  • add_argument("命令", ...):声明一个位置参数,第一个字符串是参数名。
  • choices=["add", ...]:限定只能传这几个值,传错自动报错并提示。
  • nargs="?":这个参数可有可无;default="" 给它一个默认值。
  • parse_args() 返回一个对象,用 args.命令 取参数值。

python todo.py --help,argparse 会自动打印一份漂亮的用法说明——这是 sys.argv 手写做不到的。

把上一节 todo.py 的 main() 换成 argparse 版:

def main():
    parser = argparse.ArgumentParser(description="待办事项管理器")
    parser.add_argument("命令", choices=["add", "list", "done", "del"])
    parser.add_argument("参数", nargs="?", default="")
    args = parser.parse_args()

    if args.命令 == "add":
        if args.参数:
            添加(args.参数)
        else:
            print("✗ add 后面要跟内容")
    elif args.命令 == "list":
        列出()
    elif args.命令 == "done":
        完成(int(args.参数))
    elif args.命令 == "del":
        删除(int(args.参数))

if __name__ == "__main__":
    main()
结论:练手用 sys.argv 理解原理,真写工具用 argparse——参数校验、帮助信息都是白送的。

八、进阶:pytest——用测试守护你的代码

程序写大了,改一处经常带崩另一处。你怎么保证"这次改动没把原来好的功能弄坏"?答案是写测试——用代码来验证代码。

Python 生态最流行的测试框架是 pytest

pip install pytest

写第一个测试

假设 todo.py 里有这样一个"标记完成"的函数:

# todo.py 里
def 标记完成(任务):
    return 任务.replace("[ ]", "[x]", 1)

新建 test_todo.py,写测试:

# test_todo.py
from todo import 标记完成

def test_标记完成():
    assert 标记完成("[ ] 买菜") == "[x] 买菜"      # 断言:结果应该长这样

def test_已经是完成的():
    assert 标记完成("[x] 写作业") == "[x] 写作业"    # 重复完成不报错

跑测试

在命令行敲:

pytest

全绿就说明功能正常:

test_todo.py ..                          [100%]

assert 是测试的灵魂:你断言"结果应该是什么",不对就报错。测试函数必须用 test_ 开头,pytest 才会自动找到它。

好处

  • 改代码不慌:每改完一次跑 pytest,绿了说明没改坏,红了立刻知道哪里出问题。
  • 当文档看:测试写了函数"该怎么用",别人一看就懂。
  • 逼你把代码写干净:不好测的代码,往往也是不好维护的代码。
这套思路就是"测试驱动"的雏形,也是工程级开发的标配——本系列后面那些 AI Agent 背后,同样是一堆测试在兜底。

九、动手时间 🎯

实验 1:跑通并"玩坏"它

先正常跑一遍 5 条命令,然后故意输入错误命令(比如 todo.py done 99todo.py xxx),看程序怎么友好地提示而不崩溃——这就是健壮性。

实验 2:加一个 clear 命令

给工具加第 5 个命令:clear 清空所有任务。提示:把空列表 保存任务([]) 就行。

实验 3:改造成"记账本"

把任务换成"收入/支出"记录,加一个 total 命令算总余额。存格式改成 +100 工资-30 午饭 这种,用字符串方法解析数字。

实验 4(挑战):加上日期

每条任务加个添加时间,用第 8 课的标准库 datetime

from datetime import datetime
时间 = datetime.now().strftime("%Y-%m-%d %H:%M")
任务列表.append(f"[ ] {内容}  ({时间})")

十、结业:你已经是"会写脚本的人"了

恭喜!走到这里,你已经:

  • ✅ 会写能跑的 Python 程序
  • ✅ 会读文件、写文件、处理异常
  • ✅ 会用 numpy/pandas 玩数据
  • ✅ 会联网调 API
  • ✅ 能从零写一个命令行小工具

这套能力,已经足够你无缝进入《从算法到人工智能》系列——下一站:算法、数据结构,然后是机器学习、Transformer,直到写出你自己的 AI Agent。

十一、小结

  1. 写脚本的套路:先想清楚要做什么,再拆成"读数据 → 处理 → 输出"。
  2. 命令行参数用 sys.argv,文件读写用 open(),异常用 try/except 兜底。
  3. 你已经能从零写能跑的小工具了——这是通往 AI Agent 的入口。

从零到能写脚本,你做到了。 现在,去写点什么吧。🎉

标签: none

添加新评论