☰
Python打卡30天复盘:重写旧代码与装饰器实战
2026/10/15 23:17:32 网站建设 项目流程

1. 为什么我在第30天没有学新东西

1.1 这一晚我只做了一件事:把之前的代码重新拆开

今天是我Python打卡的第30天。标题写着"王者归来",但真坐到电脑前,我没有急着打开新的教程,也没有去啃所谓的进阶框架。我把过去29天写过的东西全部翻出来,挑了一个很小的项目,从头再写了一遍。这个项目叫"个人事务管理工具",功能很简单:命令行里添加任务、查看任务、把任务标记为完成、关机之后再打开数据还在。听起来不酷,但它几乎覆盖了我这一个月学过的所有基础点:类、文件读写、异常处理、字典列表嵌套、时间处理、还有装饰器。

代码量不大,也就一百来行,但我故意写得比第一次更"端正"。第一次写的时候,我为了赶打卡,把所有逻辑都塞在几个函数里,全局变量满天飞,文件路径写死。这次重写,我给自己定了三条规矩:每个类只干一件事,每个函数不超过十五行,不允许出现两个以上的全局变量。写完再读一遍,我发现这就是"王者归来"最实在的版本——不是会了多厉害的新技术,而是能把以前的东西写得更干净。

from datetime import datetime import json from functools import wraps def log_operation(func): @wraps(func) def wrapper(self, *args, **kwargs): result = func(self, *args, **kwargs) with open("operations.log", "a", encoding="utf-8") as f: f.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} 调用 {func.__name__}\n") return result return wrapper class Task: def __init__(self, title, due_date=None): self.title = title self.created_at = datetime.now() self.due_date = due_date self.done = False def to_dict(self): return { "title": self.title, "created_at": self.created_at.isoformat(), "due_date": self.due_date.isoformat() if self.due_date else None, "done": self.done, } @staticmethod def from_dict(data): task = Task(data["title"]) task.created_at = datetime.fromisoformat(data["created_at"]) task.due_date = datetime.fromisoformat(data["due_date"]) if data["due_date"] else None task.done = data["done"] return task class TaskManager: def __init__(self, storage_path="tasks.json"): self.storage_path = storage_path self.tasks = [] self.load() def load(self): try: with open(self.storage_path, "r", encoding="utf-8") as f: raw = json.load(f) self.tasks = [Task.from_dict(item) for item in raw] except FileNotFoundError: self.tasks = [] @log_operation def add(self, title, due_date=None): self.tasks.append(Task(title, due_date)) self.save() def save(self): data = [task.to_dict() for task in self.tasks] with open(self.storage_path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2)

这段代码里有几个细节是最近几天才真正理解的。比如@staticmethod的装饰器用在from_dict上,是因为我不需要某个具体任务实例就能把字典还原成任务;而@log_operation装饰在add方法上,是为了记录每一次新增操作的日志,不需要在每个方法里重复写with open(...)这种样板代码。第30天重新写它的时候,我明显感觉到自己已经能"为了某个目的而选择某种写法",而不是照着教程敲一遍就完事。

1.2 所谓"第30天",其实不是第30个新知识点

大多数人打卡很容易掉进一个陷阱:每天必须学一个新东西,才觉得今天没白过。我前面十几天就是这样的状态,为了打卡,每天逼自己翻过一两个小节,哪怕概念根本没消化,也硬着头皮往下走。结果第18天我在写一个文件处理功能的时候,突然发现自己连最基础的文件打开模式都说不清楚r和rb到底差在哪。那一刻挺受打击的。

后来我调整了策略:不是每天学新知识,而是每天解决一个真实问题,需要什么就现学什么。第30天晚上重写这个小工具,就是这种策略的延续。它的价值不在于让我掌握某个新函数,而在于检验我对旧知识的掌握程度。实际上,我重写的过程里确实发现了一个之前留下的隐患:created_at字段在第一次实现里存的是默认字符串,这次改成isoformat()统一格式,才避免了日期比较时格式不一致的问题。如果没有这次复盘,我大概要到后面某个项目里才被这个坑炸到。

所以如果你也准备打卡,我特别建议每隔几天就停下来做一次"旧代码重写":同样的需求,看看能不能用更好的结构、更少的全局变量、更明确的函数名表达出来。这种进步未必看得见,但它比每天打卡一个新章节要扎实得多。

2. 三十天路线复盘:从一行 print 到能跑通一个本地Web应用

2.1 我给自己分的四个阶段,以及每个阶段的代表作

翻我这30天的打卡记录,表面上是一天一个小节,实际上可以分成四个阶段。第一阶段是第1到7天,重点是语法地基,包括变量、分支、循环、列表字典、基础函数。代表作是两个练习:猜数字和九九乘法表。第二阶段是第8到15天,开始接触函数进阶、模块、文件读写和异常处理,代表作是一个日志文件分析脚本,能把几百行日志里的错误级别统计出来。第三阶段是第16到23天,进入面向对象、生成器、装饰器以及常用标准库,代表作就是前面那个命令行事务管理工具。第四阶段是第24到30天,做了爬虫入门、Flask入门和最基本的部署理解,代表作是一个天气查询小工具和一个只跑在本地电脑上的订单查询页面。

这四个阶段不是按难度线性排的,中间我反复调整过。最开始的计划是一周内学完基础语法,但实际上第4天我在列表推导式和字典推导式之间绕了很久,宁可慢一点,把每个概念都用小例子验证一遍。这也是我对新手最想说的一句话:别追求三十天速成,追求的是三十天里每一天都真的动手写了一点东西。

2.2 一张表看清我这30天干了什么

时间段核心内容当天做过的最有成就感的练习卡过我最多时间的难点
第1-7天变量、分支、循环、列表字典、基础函数猜数字、九九乘法表列表推导式的执行顺序
第8-15天函数进阶、模块导入、文件读写、异常日志错误统计、批量重命名文件路径分隔符,Windows和Linux的差异
第16-23天类、生成器、装饰器、正则、常用标准库命令行事务管理工具装饰器带参数的那一层wrapper
第24-30天requests、页面解析、Flask入门、本地部署天气查询、本地订单查询页面Flask路由和静态文件路径容易让人糊涂

这张表是我今天整理打卡记录的时候顺手做的。做它的主要目的不是为了炫耀进度,而是为了看清楚自己到底在哪一类问题上消耗时间最多。数据很诚实:我花在装饰器上的反复练习,比学爬虫的时间还要长;而正则表达式到现在也只敢说会用最基础的re.search和re.findall。知道自己哪里弱,下一步才有方向。

2.3 回看这个路线,我会改掉的三个决定

第一个要改的是过早买了很多深度书。书本身没问题,但我在语法还没熟练的时候去看装饰器的高级实现,直接导致那几天极度挫败,差点断更。第二个要改的是跳过虚拟环境,后面我会专门讲这个坑。第三个是爬虫阶段抓了一个不规范的页面,解析器警告刷了一屏,我花了一整天才意识到问题出在对方页面HTML不规范,而不是我的代码写错。这三个决定如果重新来过,我都能省下至少两三天时间。

不过整体来说,这条路线还算合理:基础语法 -> 函数与文件 -> 面向对象与常用工具 -> 简单项目实战。再回头看,唯一要提醒后来人的是,每一阶段之间别急着往前走,留一天把上一阶段的小练习改成对象版本,收益会非常大。

3. 第30天最想写清楚的一课:装饰器帮我把重复代码彻底赶走

3.1 为什么我把装饰器放在最后面才认真理解

装饰器这个名词,第一次看到的时候差点把我劝退。"函数可以接收函数作为参数,还可以返回函数"这句话,我记得当时读了三遍都没转过弯来。头十几天我都绕着它走,直到第20天写命令行工具,发现几个方法里都在重复打开文件记录日志,我才意识到:如果不解决这个重复,代码就会越来越臃肿。于是第21天到23天,我专门停下来啃装饰器,从最基础的不带参数写法开始,一步步试到带参数的版本。

理解装饰器有个很生活化的切入点:把函数想象成一台咖啡机,装饰器就是在咖啡机外面装了一个计时器。咖啡机本身只负责出咖啡,计时器负责每次出咖啡之前自动记录一下时间,不用你每次手动按秒表。Python里的装饰器也是这样,它接收一个函数,返回一个新函数,在新函数里先做一些额外的事,再调用原来的函数。

3.2 手写一个日志装饰器,逐行看它干了什么

下面这个例子,是我第22天写的第一个真正能用的装饰器:

import time from functools import wraps def timer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) end = time.perf_counter() print(f"{func.__name__} 运行耗时: {(end - start):.4f} 秒") return result return wrapper @timer def compute_total(n): total = 0 for i in range(n): total += i return total print(compute_total(1000000))

我拆开解释一下里面的关键点。第一,wrapper里的*args, **kwargs必须写,因为装饰器要适配任意签名的函数,不能只针对某个固定参数。第二,@wraps(func)这行很多人初学会忽略,它的作用是把原始函数的__name__和文档字符串等元信息复制到wrapper上,否则使用compute_total.__name__会得到wrapper而不是compute_total。第三,return func(*args, **kwargs)之前的代码会在原函数执行前跑,之后的代码会在原函数执行后跑,这就是它能够同时做前置准备和后置清理的原因。

我第一次跑这个例子的时候,控制台输出的函数名确实还是compute_total,当时我愣了一下,随后才意识到是@wraps在起作用。这种细节,不亲手操作是永远记不住的。

3.3 从无参装饰器到带参装饰器:只多了一层壳

等到第23天,我开始遇到需要给装饰器传参的场景。比如我想控制日志写到哪个文件,就不能写死在一个固定的日志路径里,必须写成@log_to("app.log")这种形式。那时候我才明白,带参数的装饰器其实只是在外层再包一个函数:

def log_to(filename): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): with open(filename, "a", encoding="utf-8") as f: f.write(f"{func.__name__} 被调用\n") return func(*args, **kwargs) return wrapper return decorator @log_to("custom.log") def say_hello(name): print(f"hello {name}") say_hello("python")

关键是要分清楚每一层在干嘛:最外层的log_to接收文件名参数,并返回一个真正的装饰器;中间的decorator接收被装饰的函数;最里面的wrapper接收函数调用时的参数。三层各司其职。初学的时候这层嵌套很容易看晕,但拆开之后其实就是一个"参数传递"的套娃,没有魔法。

3.4 在真项目里,装饰器帮我省掉了几十行重复代码

回到第30天重写的那个事务管理工具,我在add方法上用了log_operation装饰器之后,后来又给done方法也加上了同样的装饰器,只改动了一个单词。如果不用装饰器,我需要在每个方法内部重复粘贴打开日志文件、写入、关闭这段逻辑,改日志路径的时候还要到处找。装饰器真正的价值不是让代码看起来更"高级",而是让后续维护变得安全:我只需要在一处修改,所有被装饰的方法都会自动跟着变。

当然,装饰器也不是用得越多越好。如果一个项目里到处是嵌套装饰器,会严重降低代码可读性。我给自己定的标准是:只有在多个函数确实共享同一段"前置或后置逻辑"的时候才用装饰器,否则还是老老实实写普通函数调用更直观。

4. 踩坑实录:这30天里最让我想放弃的五个错误

4.1 全局变量修改:以为Python会自己记住

打卡第9天,我写了一个统计数字出现次数的函数,想在全局变量里累计结果,代码如下:

count = 0 def add_one(): count = count + 1 add_one() print(count)

运行直接报UnboundLocalError。我当时一脸懵,明明全局变量叫count,函数里不就是在改它吗?后来才明白,只要函数体内部出现了给变量赋值的语句,Python就会把它当成局部变量,因此count = count + 1右侧的count会被优先解释为尚未定义的局部变量。解决方案是在函数内声明global count,或者更干净的做法是让函数用返回值更新变量。这个坑非常典型,第9天踩到的时候我甚至怀疑自己是不是不适合学编程。

4.2 列表复制:三分钟写完的代码,三天后才发现数据被一起改了

第16天我写批量处理数据的小脚本,为了保留原始数据,写了一句new_list = old_list。结果我发现修改new_list里的元素时,old_list也跟着变了。原因在于列表是可变对象,new_list = old_list只是复制了一个引用,两个变量指向内存里的同一个列表。要真正复制,必须用new_list = old_list.copy()或者是old_list[:]。这个错误最坑的地方在于它不会立刻报错,而是在逻辑完全正确的情况下产生隐蔽的错误结果,排查起来特别费神。

更隐蔽的版本是列表里套了可变对象,比如[["a"], ["b"]],直接用.copy()也只复制了外层,内层字典或列表的引用还是同一个。那段时间我被多维数据处理折磨得不轻,最后养成了一个习惯:只要涉及复制,就默认思考一层够不够,通常要用copy.deepcopy才能彻底切断所有引用关系。

4.3 文件编码:别人能打开的脚本,我这边乱码

第14天我拿到一个日志文件,想统计里面有几次报错。脚本写得很顺,结果一运行,中文全部变成乱码,程序还抛了编码异常。原因是我在Windows上打开文件用的默认编码是gbk,而对方文件是用utf-8存的。从那以后我读到"编码"两个字都会多看一眼。处理非纯英文文件时,打开文件统一加encoding="utf-8"是最省心的做法;如果文件来源不明,可以用errors="ignore"或者是先读取字节再猜编码。

这个坑看起来很基础,但它能让你在一个细节上卡掉一个晚上。尤其当你从别人那里拿代码、拿数据文件时,编码问题几乎无处不在。现在我的习惯是写文件的时候手动指定encoding="utf-8",读文件的时候也手动指定,不依赖系统默认值。

4.4 全局环境装了一堆"孤儿包"

第24天我进入爬虫阶段,需要用requests,于是直接pip install requests。装完之后发现系统提示权限不足,我又去搜命令行,看到说加--user参数,装了之后倒是能用了。但接下来又出现版本冲突,一个库要求idna高版本,另一个库要求低版本,全局环境被我搞得越来越乱。最后我一怒之下把Python卸载重装,才明白最正确的做法是从一开始就建一个虚拟环境。

虚拟环境解决的是一个非常痛的问题:不同项目依赖不同版本的第三方库,如果全部塞进全局环境,迟早会打架。现在我每次新建项目文件夹,第一句话就是python -m venv venv,然后在编译器里选择这个环境作为解释器。刚开始会觉得麻烦,多建几次之后就会发现这是最省心的习惯,没有之一。

4.5 以为看懂报错,其实只看了最后一行

Python的报错信息其实已经非常友好了,但我前面经常犯一个错误:只盯着SyntaxError或NameError这种最后一行的异常类型,没有往上翻具体是哪一行、哪个变量出了问题。有一次报错发生在某个函数内部,我反复看函数的最后一行,怎么都想不通,后来才注意到报错信息里明确指出了"调用链":先是某个地方调用了这个函数,函数内部某一行触发了条件。顺着调用链往回看,问题瞬间就清楚了。

有经验的开发者看报错会先看最下面的异常类型,再往上找代码文件和行号,最后才看具体信息。遇到看不懂的报错,把整段提醒复制下来搜索,也是一个效率很高的办法。报错不是来找茬的,它是最直接告诉我们程序哪里和预期不符的工具。

5. 能坚持30天打卡的,不是意志力,是一套"最小可行日"机制

5.1 我失败的早起学习计划

其实打卡的第一周,我给自己的计划是每天早上六点起床学两个小时。听起来很热血,实际上坚持了三天就崩了。第三天晚上加班到十一点,第二天根本起不来,错过一天之后心里全是愧疚感,差点直接放弃整个计划。后来我才明白,把赌注全部压在"每天固定两小时"上,只要生活出现一点点波动,整个计划就会碎掉。

5.2 我最终固定下来的打卡规矩

真正让我撑过30天的,是一套看起来特别不起眼的规矩:每天必须打开代码编辑器,即使只写十行代码,也算完成打卡。我只要求自己完成一个"最小可行日"——学一个新函数、修一个旧bug、或者只是把之前没跑通的代码让它跑通,都可以。状态好的时候,自然会继续往深处学;状态差的时候,十行代码也不至于让人反感。

另一个关键动作是固定时间。我选择的是晚上九点半到十点,这个时间不需要早起,也不容易被工作干扰。固定时间最大的好处是降低决策成本,我不需要每天纠结"什么时候学",到点坐在桌前就行。第三个关键动作是公开记录,我在自己的公开打卡帖里每天写一行总结。公开就像一个温和的压力阀,让我不好意思随便断更,但又不会像没完成KPI那样恐惧。

5.3 断更之后怎么办:绝不连续补卡

第18天的时候我因为临时出差,晚上在火车上没信号,没能打卡。第二天回来之后我第一反应是想把昨天和今天的学习量一起补上,但冷静想了想,还是决定只按今天的量正常学。连续补卡最容易带来的结果就是某一天为了追进度而放弃深度,最后不了了之。断一天就断一天,把它当作计划里允许存在的"弹性空间",反而不会影响长期节奏。

另外我想说,打卡本身不是目的,它只是帮你维持一个最低频率的接触。真正产生进步的是每一次接触里的专注度。哪怕某天只有三十分钟,只要这三十分钟里我完完整整跑通了一个脚本、看懂了一个机制,就比坐在那里刷两个小时教学视频要强得多。

5.4 工具准备不用多,一台电脑加一个代码仓库就够了

我还看到有人问打卡需要买哪些付费课程、要不要配个高配电脑。就我这30天的亲身体验,这些东西统统不需要。一台能打开浏览器的普通电脑就足够,甚至某些基础练习在手机上也能完成。代码仓库我用的是当前最主流的在线代码托管平台,免费额度足够个人学习使用。每天写完了就提交一次,提交信息里写清楚今天做了什么。这个过程让"打卡"变得很有仪式感,翻提交记录的时候也能看到自己一天一天的变化,那种满足感是任何课程都替代不了的。

6. 第31天之后我不会再做的事

6.1 下一阶段:把"输入"换成"输出"

第30天晚上做完复盘,我给自己的第31到60天定了一个小小的输出计划:每三天完成一个可以给别人用的小工具,比如把事务管理工具加上一个简单的Web界面、写一个批量压缩图片的脚本、整理一份自己看得懂的Python常用代码段。重点是这些东西都要真实使用,而不是练习题。练习题的缺点是它已经被设计好了,只要照着提示走就能出结果,真实工具则会出现各种意外,而处理意外才是能力增长最快的地方。

我还准备把之前踩过的坑整理成一份备忘清单,按变量作用域、复制、编码、环境、报错排查几个分类记录。以后遇到相同问题,先查自己的清单,再去搜索资料。这份清单目前已经有十几条,我相信到60天的时候它会厚很多。

6.2 对刚开始打卡的人,我最想说的三句话

第一句:别被打卡天数绑架,第30天不比第3天高级多少,重点是你有没有真的留下点什么。第二句:请一定把代码亲手敲一遍,看着视频里老师敲和自己敲,完全是两回事。第三句:遇到完全看不懂的知识点,可以先跳过,后面某个真实需求会把你拉回来。我是真的在装饰器上跳了十几天之后,才在写日志的刚需里把它彻底理解的。

回头看这30天,最让我开心的不是"我学会了Python"这句话,而是我已经把它当成一个可以随手使用的工具:批量改文件名、整理表格、抓网页数据,都能自己写脚本解决。"王者归来"可能不是终点,是我终于不再把自己当成一个只能看教程的新手了。如果你也在打卡,我想说,坚持下来的每一天,都会在未来的某个时刻连成一条你意想不到的路线。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询