拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

自学编程第十八天:用Python写一个通讯录命令行工具,踩坑与复盘

自学编程第十八天:用Python写一个通讯录命令行工具,踩坑与复盘

今天是开始自学IT的第十八天。三个星期前,我还分不清Python和“蟒蛇”有什么关系,现在居然能独立写一个带命令行交互、能保存数据到文件的小工具了。这种转变谈不上多厉害,但它让我第一次对“坚持”这两个字有了实感——特别是当你处在最容易放弃的第三周,身边没人指导、网上课程越囤越多、自己写的代码一运行就报错,能继续往前挪一步都算是胜利。

这篇文章是我第十八天学习记录的完整复盘,内容包括当天的学习目标、具体代码示例、踩过的三个典型坑、以及我调整过后的学习方法。如果你也在自学编程,或者正准备开始学,希望这份记录能让你少走一点弯路。毕竟自学的路上最难的不是找不到资源,而是找不到一个真实的人告诉你:这一步会卡住,很正常,原因是这样的。

1. 自学第十八天的整体状态与学习计划

1.1 为什么第十八天是个关键节点

很多人把自学计划定在“21天”,但真正体验过的人都知道,第十天到第二十天才是淘汰率最高的一段。第一天和第二天全是新鲜感,第五天还能靠热情撑着,到了第十八天,热情基本烧完了,习惯又没有完全养成。你会开始怀疑自己是不是材料不够、智商不够、还是压根就不适合走这条路。

我的第十八天正好卡在这个节骨眼上。前一天晚上,我学完了一段关于函数定义的视频课,听得懂,但关上屏幕后什么都写不出来。那种“眼睛会了、手不会”的感觉非常消耗信心。所以第十八天我临时调整了策略:不看新视频,不学新语法,只做一件事——用已经学过的内容,硬写一个完整的小项目。

这个决定带来一个很直接的变化:当我不再被“下一个知识点”追赶时,注意力终于能放在“把代码写对”上。以前看视频时觉得函数、字典、循环这些概念都很简单,自己动手才发现,把它们组合在一起会出现无数个之前没见过的错误。第十八天恰好是检验基础是否扎实的好时机,如果拖到一个月后再来补基础,代价会大得多。

1.2 当天的学习目标拆解

早上我先用十五分钟列了当天的目标,没有贪多,只定三件事:

  • 复习函数定义与调用,特别是参数传递的细节;
  • 复习字典的增删改查操作,并练习多层嵌套;
  • 把前面学过的知识串成一个完整的命令行通讯录程序。

这三件事看起来不多,但实际操作下来,从早上九点到中午十一点半,我连代码带查错一共用了两个半小时。中途一度想放弃,但最终还是把它跑通了。事后复盘,我觉得目标拆解得越具体越好,比如“今天学会字典”这种目标基本等于没定,因为字典牵扯到增删改查、遍历、嵌套、默认值,任何一个细节卡住都会让你觉得“没学会”。把目标细化成“用字典实现联系人的新增和删除功能”,你才知道今天到底有没有完成任务。

2. 今天敲的核心代码:一个联系人管理小工具

2.1 功能设计与伪代码

写代码之前,我先在纸上画了一下这个工具大概长什么样。很多初学者习惯打开编辑器就噼里啪啦敲代码,结果敲到一半发现逻辑理不清,又全部删掉重来。我的做法是先写伪代码,把流程固定住,再动手实现。

这个联系人工具的需求很简单:用户可以在终端里输入命令,完成以下操作——添加联系人、查看所有联系人、修改联系人的电话号码、删除联系人,最后还要能把数据保存到本地文件里,下次打开时不会丢。

对应的伪代码如下:

循环显示菜单: 1. 添加联系人 2. 查看联系人 3. 修改联系人 4. 删除联系人 5. 保存并退出 用户输入选项后,执行对应操作

伪代码写完后,我心里大概有数了:整体结构要用while True来维持菜单循环,每个操作分别定义成函数,所有联系人数据用一个字典来保存,键是姓名,值是电话号码。结构定了之后,代码写起来就没那么慌。

2.2 完整代码与运行效果

下面是我当天写的完整代码,虽然和网上那些优雅的示例有差距,但这是我自己一步一步调出来的版本:

import json contacts = {} def add_contact(): name = input("请输入联系人姓名:") if name in contacts: print("联系人已存在,如需修改请选择修改功能。") return phone = input("请输入联系电话:") contacts[name] = phone print(f"已添加联系人:{name}") def show_contacts(): if not contacts: print("通讯录为空。") return for name, phone in contacts.items(): print(f"{name}: {phone}") def update_contact(): name = input("请输入需要修改的联系人姓名:") if name not in contacts: print("联系人不存在。") return new_phone = input("请输入新的联系电话:") contacts[name] = new_phone print(f"已更新 {name} 的电话号码。") def delete_contact(): name = input("请输入需要删除的联系人姓名:") if name not in contacts: print("联系人不存在。") return del contacts[name] print(f"已删除联系人:{name}") def save_to_file(): with open("contacts.json", "w", encoding="utf-8") as f: json.dump(contacts, f, ensure_ascii=False, indent=4) print("通讯录已保存到 contacts.json") while True: print("\n===== 通讯录管理 =====") print("1. 添加联系人") print("2. 查看联系人") print("3. 修改联系人") print("4. 删除联系人") print("5. 保存并退出") choice = input("请输入操作编号:") if choice == "1": add_contact() elif choice == "2": show_contacts() elif choice == "3": update_contact() elif choice == "4": delete_contact() elif choice == "5": save_to_file() break else: print("无效输入,请重新输入。")

运行效果大概是这样的:

===== 通讯录管理 ===== 1. 添加联系人 2. 查看联系人 3. 修改联系人 4. 删除联系人 5. 保存并退出 请输入操作编号:1 请输入联系人姓名:张三 请输入联系电话:13800138000 已添加联系人:张三

代码写完的那一刻,我确实挺兴奋的,因为这是第一个能“存住数据”的程序。但后来我也发现了它的问题,比如没有异常处理、没有输入校验、数据量大了以后不好查。可对我来说,第十八天能跑通这个,意义在于我把学过的内容真正用了一次,而不是继续对着屏幕看别人演示。

3. 今天踩过的坑与排查实录

3.1 编码问题:中文乱码并不都是代码的错

第一个让我抓狂的问题发生在保存文件时。我用open()写入联系人数据,最开始没有指定encoding,在 Windows 终端里运行的时侯中文全部变成了乱码。我把代码来回改了好几遍,最后才反应过来,问题不在于代码逻辑,而在于文件读写默认编码和终端编码不一致。

解决办法是在打开文件时明确指定编码:

with open("contacts.json", "w", encoding="utf-8") as f: json.dump(contacts, f, ensure_ascii=False, indent=4)

这个坑给我最大的教训是:遇到中文乱码,先分清是终端显示乱码还是文件存储乱码。如果是终端显示乱码,可以在代码开头加一行# -*- coding: utf-8 -*-,或者调整终端代码页;如果是文件内容乱码,基本就是读写时编码指定不一致。当时我把encoding="utf-8"加到读取和写入两处之后,问题彻底解决。

3.2 默认参数陷阱:数据串味的真正原因

我后来还踩了一个非常隐蔽的坑。为了一些提示信息,我写了一个类似这样的函数:

def add_tag(tag_list=[]): tag_list.append("新标签") return tag_list

第一次调用没问题,第二次调用时,返回的列表里莫名多出了上一次的数据。奇怪的是,这个列表里的数据不会被多次添加。查了很久才发现,Python函数的默认参数是在定义时创建的,同一个列表对象会一直被使用。

解决办法是使用不可变值作为默认参数,或者干脆传入None,在函数内部再创建列表:

def add_tag(tag_list=None): if tag_list is None: tag_list = [] tag_list.append("新标签") return tag_list

这个坑对初学者来说特别容易踩,因为语法上完全合法,逻辑上却和直觉相反。我个人现在写函数时,已经习惯检查所有默认参数,凡是可变类型一律不用。

3.3 变量作用域:局部变量为什么“变没了”

第三个坑出现在我修改联系人信息时。函数里我写了一段更新电话号码的逻辑,运行后发现通讯录里的数据根本没变。后来我才意识到,问题出在一个同名的局部变量上。我在函数内部对变量赋值时,Python会默认把它当成一个新的局部变量,除非我显式声明要修改全局变量。

虽然在这个小工具里,我很快意识到不用函数修改全局变量更好,但更合理的做法是让函数接收一个字典参数,然后修改这个字典的内容。因为字典是可变对象,函数内部修改字典,外部也会跟着变:

def update_phone(count_dict, name, new_phone): count_dict[name] = new_phone

这个思路也解决了后面一个问题:当我学会分模块写代码之后,经常会因为全局变量太多导致调试困难,用传参的方式反而更好维护。

4. 自学习惯与效率方法论复盘

4.1 从被动看视频到主动写笔记

第十八天结束后,我对学习方法有了一个明显的调整。前半个月我主要在看视频,看的时候觉得自己全会,实际上手就废。这不是某个人的问题,而是视频学习天然有“被动接收”的特点,大脑很容易产生已经在学会了的错觉。

后来我改成了“视频只看50%,剩下50%用来写笔记和做练习”。看视频每讲完一个知识点,我会暂停,然后把代码抄一遍,自己解释一遍。重点不是把代码背下来,而是搞清楚每一行在原知识体系里的位置。比如学到函数参数时,我给自己写了一个小卡片,记录位置参数、默认参数、可变参数的区别,并用表格对比:

参数类型定义写法调用方式常见坑
位置参数def f(a, b)f(1, 2)顺序不能错
默认参数def f(a, b=3)f(1)可变值不能当默认参数
可变参数def f(*args)f(1, 2, 3)得到元组,不是列表

这种笔记方式帮我把脑子短暂记住的内容留在了纸面上。现在翻看前几天的笔记,很多当时一知半解的地方已经自然理解了。

4.2 最小反馈闭环:写代码、看报错、修bug、做记录

我还给自己定了一个“最小反馈闭环”原则,用四个步骤循环:

  • 写一小段代码,只做一件事;
  • 运行它,故意让它报错,或者观察输出是否符合预期;
  • 如果出错,先读最后一行错误信息,再往上追代码;
  • 修复后,把问题和解决思路记录到当天的日志里。

这个习惯帮了我大忙。以前一看到报错就慌,动不动全屏截图求助。后来发现大多数错误信息都有固定的套路,比如NameError说明变量没定义、TypeError说明类型不对、IndexError说明越界。读到错误类型后,再去查具体行数,效率高多了。

第十八天那天我记录的一个典型错误是KeyError: 'name',当时我以为是字典的问题,后来才发现是数据文件里根本没有这个键。为了排查,我在程序里临时加了一行调试输出:

print(contacts.keys())

看到输出结果后,真相一目了然。我现在遇到奇怪的问题,第一反应不是找大神,而是先打印出来看看数据到底是什么样。很多时候,问题只是存在信息和理解不一致。

5. 下一步学习规划与给同类自学者的话

5.1 面向对象和第二遍重构

第十九天开始,我准备进入面向对象的学习。但我给自己定了一个不一样的目标:不是看完面向对象的视频课,而是把通讯录小工具用面向对象的方式重写一遍。也就是说,把现有的函数式代码改造成用类来组织逻辑。用一个ContactBook类来管理所有联系人,把之前散落的函数变成类的方法。

这样做的原因很简单,语法只是表面,真正要理解面向对象,必须亲自动手把一个已经能跑的项目改造一遍。改造过程中会遇到很多新问题,比如对象的属性怎么设计、方法之间怎么共享数据、类变量和实例变量有什么区别。这些问题光靠看视频解决不了,但如果你在重构中遇到它们,再回去看视频,理解会完全不一样。

我给自己规划了接下来七天的方向:

天数学习主题练习目标
第十九天类与对象基础为通讯录写一个ContactBook类
第二十天继承与会话实现一个更复杂的命令行交互逻辑
第二十一天文件读写进阶用 CSV 格式导出通讯录
第二十二天异常处理为所有输入操作添加异常处理
第二十三天小型项目重构把通讯录工具改造得可用、规范
第二十四天单元测试入门为通讯录写几个简单的测试用例
第二十五天学习复盘把前几天的成果整理成项目总结

这个计划不一定适合所有人,但对我来说,它解决了“下一步不知道学什么”的焦虑。每天都有一个明确的终点,哪怕进度慢一点,也比东一榔头西一棒子强。

5.2 给同样自学的朋友几句大实话

第十八天那天晚上,我坐在书桌前复盘,脑子里冒出来几句话,可能对同样走在自学路上的人有点帮助。

第一,不要和别人比进度。我在一些论坛里看到有人十五天就学完了别人一个月的内容,心态差点崩了。后来想通了,自学是长跑,不是短跑。别人学得快和你没关系,你学得慢也不代表最后走不到终点。

第二,一定要动手敲代码。看视频、看别人写的代码示例,和自己从空白文件开始写,难度差了不是一点半点。哪怕只是照着示例敲一遍,也比光看强十倍。

第三,报错不是坏事。第十八天之前,我一看到Traceback就头大,觉得是自己不行。后来才意识到,每个报错都在告诉我代码的哪一行出了问题,这比我满屏逻辑错误却没有任何提示要友好得多。把报错当成老师的批改,心态就顺了。

第四,不用囤资料。我的网盘里躺着十几个教程、几十本电子书,真正看完的不到两本。到现在我终于明白,教程的作用只是把人领进门,真正能让你进步的是不断写代码、不断解决问题的循环。把一套资源吃透,远胜于收藏十套。

第五,允许自己有学不会的地方。第十八天,我仍然搞不懂装饰器的原理,也不理解 Python 的 GIL 到底是怎么回事。但没关系,这些大概率不会影响我继续往前走。把暂时用不到的知识放到“待学习清单”里,等真正需要的时候再回来专攻,效率往往更高。

自学第十八天,我依然谈不上什么 IT 从业者,但至少我不再像刚开始那样觉得编程是一门玄学。它是有规律可循的,是一步一步的工程过程。第天写几行代码、修几个 bug、记几条笔记,把时间拉长,你会看到一个肉眼可见的进步曲线。这就是我第十八天最大的收获。

返回列表