ARTICLE

Python学习第三阶段重点:函数、数据结构、文件与面向对象

考试通知 · 政策解读 · 开班计划

发布时间:2026/10/12 12:59:13来源:迅启考通分类:考试资讯

文章详情

以下为资讯详情页模板:正文区域由后台内容渲染,图片与正文将自动替换为对应文章内容。

文章配图
Python学习第三阶段重点:函数、数据结构、文件与面向对象上个星期我收到一条私信大意是跟着Python-100天从新手到大师这个学习计划走到第三周前面的变量、循环、字符串都能看懂但一进入新内容就觉得吃力。我回了一句恭喜你正处在整个计划最关键的转折点。今天这篇学习笔记三不讲环境安装也不再重复基础语法我把自己在第三阶段梳理出来的重点——函数与模块、数据结构进阶、文件读写与异常处理、面向对象入门——按实际踩坑的顺序重新过一遍。这篇笔记适合正在赶进度的人、半路卡住的人以及那些学了一阵子却总觉得代码写不利索的朋友。1. 阶段定位第三篇笔记到底学到了哪一步1.1 走到这里你手里的牌已经不一样了如果按照常见的学习进度前两篇笔记应该覆盖了Python环境搭建、变量与数据类型、分支结构、循环结构以及字符串、列表、元组这些基础容器的简单操作。这个阶段的特点是“看得懂”因为语法规则就是那么几条跟着敲一遍基本都会。但到了第三篇学习模式会发生一个明显变化从“认识语法”变成“组织代码”。这个变化有多重要我举个例子。前两周你写一个求平均数的程序可能是定义几个变量、算一下、打印结果一个文件从上写到下就完了。可一旦程序要处理五十个学生的成绩、要按班级分组、要把结果存成文件再往后还要给不同身份的用户展示不同界面你就不可能继续用那种平铺直叙的写法了。函数、模块、数据结构、文件读写、异常处理、面向对象这些东西会在这个阶段集中出现目的只有一个让你能写出“能长大”的代码。1.2 这一阶段的路线地图第三篇笔记覆盖的内容可以整理成一张路线图。我在实际学习时是这么安排的学习板块核心目标常见误区函数与模块把重复代码封装成可复用的单元只抄语法不理解参数传递和命名空间数据结构进阶在合适的场景选合适的容器什么类型都用列表不懂字典和集合的价值文件读写与异常让程序和外部数据交换并处理失败情况不关文件、不处理编码、异常一吞了之面向对象入门用类和对象组织真实业务逻辑把类当成语法背诵不理解封装和继承的意义综合实战把以上知识串成一个小工具只做题不写项目学完就忘这张表的顺序不是我随便排的它暗含了一条依赖关系函数和模块是代码组织的底座数据结构决定数据怎么存、怎么查文件读写让程序有了持久化的能力异常处理保证程序在出错时不至于崩溃面向对象则把这些能力进一步整合成贴近真实业务的模型。一条线走下来前面的内容会反复在后面被用到这也是为什么很多100天计划会把它们放在连续的几天里。2. 函数与模块从写脚本到搭积木的关键一跃2.1 为什么函数是你第一个必须“较真”的知识点我在学习阶段见过很多同学写出几百行没有函数的脚本特点是顶上一堆变量中间全是if和for改一个逻辑要用编辑器的全局替换稍不留神就把别处的代码也改了。这种代码不是不能跑而是没法维护。函数解决的是三个问题。第一个是复用同样的计算逻辑写一次之后到处调用第二个是隔离函数内部的变量默认不污染外部各个功能块之间的影响降到最低第三个是可测试把一段逻辑单独拎出来给它输入看输出调试效率比在几百行代码里找问题高得多。这里我多说一句初学者最容易犯的毛病是“懒得封装”总觉得代码短、没必要。我的建议是哪怕只有三行重复逻辑也值得抽成函数。因为你是在练习一种组织习惯而不是在写一次性的脚本。等以后做爬虫、做数据处理代码量上来之后好习惯会直接决定你能不能继续推进度。2.2 参数类型搞不明白记住一个调用场景就通了Python的函数参数新手最容易晕的就是位置参数、关键字参数、默认参数、可变参数混在一起。别死记规则直接看一个典型的调用场景。def register_user(name, age18, *tags, **info): # name 是必填位置/关键字参数 # age 有默认值调用时可以不传 # tags 收集多余的位置参数打包成元组 # info 收集多余的关键字参数打包成字典 print(name, age, tags, info) # 各种合法的调用方式 register_user(小明) register_user(小明, 20, 学生, 内测用户) register_user(小明, city北京, job工程师) register_user(小明, 20, VIP, city上海, level3)这里最核心的一点是参数顺序先写必填参数再写带默认值的参数然后是*args最后是**kwargs。*args把所有多余的按位置传入的参数打包成元组**kwargs把所有多余的关键字参数打包成字典。我在学习时把它们理解成两个“收纳袋”一个是收零散东西的袋子一个是收带标签东西的袋子程序运行时到底收到什么打印出来一目了然。还有一个容易被忽略的细节默认参数不要用可变对象。比如def func(items[])这种写法是个经典坑因为默认列表在多次调用之间是共享的第一次调用往里面加的数据第二次调用还能看到。正确的做法是写成def func(itemsNone)函数内部再用items [] if items is None else items来初始化。这个坑我在写一个小型日志收集程序时踩过查了半天才发现是默认列表在作祟。2.3 作用域与闭包不用背LEGB用直觉理解函数带来的变量隔离本质上就是作用域问题。Python查找变量时规则是从内向外找先找函数局部再找嵌套函数的外层再找模块全局最后找内置名称。这个规则被总结成LEGB初看不难但真正困扰新手的是“闭包”。闭包可以这样理解外层函数定义了一个变量内层函数引用了它并且外层函数把内层函数作为返回值交出去。内层函数被调用时即使外层函数已经结束它仍然能记住外层那个变量。def make_counter(step): count 0 def counter(): nonlocal count count step return count return counter add_one make_counter(1) print(add_one()) # 1 print(add_one()) # 2这个例子里的nonlocal count是必须写的。如果不写内层函数里count step会被当成定义一个全新的局部变量导致运行时报错。我在学习时用一句话帮助记忆闭包的本质是“函数带着数据出走”而nonlocal告诉Python这个变量我要修改的是外层那个不是新建一个。2.4 模块把函数装进不同的“工具箱”当函数越来越多把它们都塞在一个文件里就又回到了老问题文件太长、职责不清。模块的出现就是给代码分门别类。以我的经验初学者只需要掌握三种导入方式就够了import module、from module import name、from module import name as alias。这里有一个真正的坑from module import *。这种写法会把模块里所有不以下划线开头的名字全部导入当前命名空间看起来省事实际危害很大。你无法确定哪些名字是自己定义的哪些是导入的遇到名字冲突时很难排查。我在学习阶段曾经被这种写法坑过一次两个模块里都有read_data函数结果后导入的覆盖了先导入的程序跑出来的结果完全不符合预期。从那以后我的习惯是要么导入整个模块要么明确导入需要的名字。自定义模块还有一个必须懂的写法# 文件 my_tools.py def helper(): print(help) if __name__ __main__: # 只有直接运行这个文件才会执行 helper()if __name__ __main__的作用是区分“直接运行”和“被导入”。直接运行时__name__变量是__main__被导入时它是模块名。这个写法几乎每个Python项目都在用学习阶段必须养成习惯否则一导入模块就执行一堆测试代码会让人非常头疼。2.5 标准库随手用别重复造轮子进入模块阶段后一个重要的意识是Python自带的标准库已经解决了大量常见问题。我在这篇笔记阶段用得最多的是这几个random生成随机数、随机选择、打乱列表。datetime日期时间计算、格式化输出。collections其中的Counter能快速统计频次defaultdict能避免键不存在的报错。json把数据转成JSON字符串或从JSON字符串恢复数据前后端交互、配置文件解析都会用到。有些同学会担心“用库是不是就不算自己实现了”这个担心没必要。标准库本身就是语言的一部分就像你用空调不一定要自己造压缩机。真正该练习的是遇到需求时先想标准库有没有现成方案没有再去造轮子。这一个习惯会让后面的学习速度快很多。3. 数据结构进阶再也不用什么数据都塞进列表3.1 四大基本容器的选型标准Python的list、tuple、dict、set这四样东西在初学阶段都学过但很多人到第三篇才开始真正理解它们的差异。我用一个需求说清楚假设你要管理一批用户有时候需要按顺序遍历有时候要根据用户名快速查找有时候要统计所有不重复的状态值。这种“按场景选容器”的能力就是数据结构进阶的核心。容器可变性有序性是否允许重复典型场景list可变有序允许保持顺序的待办事项、榜单tuple不可变有序允许坐标、配置项、字典的键dict可变插入有序Python 3.7键唯一按名字查信息、映射关系set可变无序元素唯一去重、交集并集差集运算这张表里最关键的是第一列和最后一列。可变性决定了一个对象能不能被安全地当作字典键也决定它在函数参数传递时会不会被意外修改典型场景决定了你写程序时的手感看到需求就能反应出用哪个容器。3.2 列表与元组可变和不可变不是小事初学阶段很多人觉得元组就是“不能改的列表”这句话没毛病但不够深入。元组的不可变性带来了两个重要价值一是可以作为字典的键而列表不行二是在多线程或复杂逻辑中传一个永远不会被意外修改的对象能省掉很多调试精力。举个例子一个平面坐标系上的点用元组(x, y)来存就非常合适。如果有人试图给这个点赋值程序会立刻报错这其实是在帮你发现问题。而待办清单这种需要增删改的数据用列表就对了。我个人的学习心得是默认不要什么都用列表当你知道这批数据不会被修改就试试元组代码的语义会更清楚。3.3 字典的快速查找活在“带索引的字典”里字典是Python里查找效率最高的容器之一。它的底层机制是哈希表简单理解就是存数据时会根据键计算出一个位置取数据时同样根据键计算位置直接跳过去拿不需要逐个遍历。这也解释了为什么字典的键必须是不可变对象——如果键在存进去之后变了哈希值变了原来的位置就找不到了。实际写代码时字典的几个方法比新手想象中好用得多。最典型的是getuser_scores {小明: 90, 小红: 85} # 方式一直接取键不存在会报错 # score user_scores[小刚] # 方式二get方法键不存在返回默认值 score user_scores.get(小刚, 0)我见过太多人写if key in dict: value dict[key] else: value 默认值这种三行代码其实一个get就搞定了。类似的还有setdefault它能在键不存在时设置默认值并返回非常适合统计频次。3.4 集合的两把刷子去重和关系运算集合最大的特点是元素唯一所以“去重”是它的招牌功能。一个列表里有重复数据直接set(数据)转一下再去重比手写循环判断高效得多。还有一类场景非常实用求两个集合的交集、并集、差集。black_list {user01, user02, user03} vip_list {user02, user04, user05} # 找既在黑名单又是VIP的用户 target black_list vip_list # 找所有涉及的用户 all_users black_list | vip_list这里我用的是运算符写法Python对集合重载了、|、-这些符号分别表示交集、并集、差集。代码读起来比调用方法名更直观这也是集合在业务逻辑里经常出现的原因。3.5 推导式让代码短一截也不失可读性推导式是Python特有的写法本质是“用表达式生成容器”。刚接触时可能会觉得眼花但用熟之后真的回不去了。# 列表推导式生成0到9的平方 squares [x * x for x in range(10)] # 字典推导式生成名字到姓名长度的映射 names [小明, 小红, 小刚] name_len {name: len(name) for name in names} # 集合推导式去掉重复并取长度大于等于2的元素 words [hi, hello, hey, hi] long_words {w for w in words if len(w) 2}我的经验是推导式不要一开始就追求复杂写法。掌握了“for之前写表达式for后面写循环条件最右侧写过滤条件”这个结构后先做几个简单例子再慢慢增加条件。如果在推导式里塞三层嵌套循环加一堆条件可读性会急剧下降那时候用普通循环反而更合适。4. 文件读写与异常处理程序开始有“记忆”了4.1 读写文件的正确姿势模式、编码、上下文管理器到了这个阶段你的程序可以把自己的状态保存下来了。读文件、写文件看着简单但藏着不少坑。打开文件时的模式决定了你是读还是写是用文本模式还是二进制模式这个必须记清楚。# 写入文件 with open(config.txt, w, encodingutf-8) as f: f.write(你好Python) # 读取文件 with open(config.txt, r, encodingutf-8) as f: content f.read() print(content)这里最值得强调的是with语句也就是上下文管理器。它保证文件在代码块结束后会被自动关闭即使中间发生异常也一样。我见过一些老代码用f open(...)然后f.close()问题是如果read或write过程中抛异常close那一行可能根本执行不到文件句柄就会一直占用后续程序可能因为文件被占用而报错。编码问题也是初学者掉坑重灾区。Python默认编码在不同平台可能有差异我的习惯是读写文本文件时统一指定encodingutf-8。如果遇到的是Windows上常见的GBK编码文件就明确写成encodinggbk千万别依赖系统默认值。另外用w模式写文件会清空原有内容如果只想追加要用a模式。这是两个最常见的文件读写事故。4.2 处理CSV和JSON让数据能交换真实的程序不可能总在Python自己的格式里打转。CSV适合表格数据JSON适合结构化数据这两个格式在爬虫、数据分析、配置管理里几乎无处不在。处理CSV时直接用标准库的csv模块不要自己手动按逗号切分因为字段里可能自带逗号、换行。import csv with open(scores.csv, w, encodingutf-8, newline) as f: writer csv.writer(f) writer.writerow([姓名, 分数]) writer.writerow([小明, 90]) writer.writerow([小红, 85])这里有个反直觉的细节写CSV文件时要传newline否则在Windows平台上每行后面会多一个空行。这个问题我当年查了好久最后才发现是换行符被重复处理了。JSON处理则要分清四个函数json.dumps把Python对象转成字符串json.loads把字符串转回Python对象json.dump把Python对象直接写入文件json.load从文件读取JSON并转回Python对象。一个容易踩的坑是中文输出变成\uXXXX转义序列解决方法是json.dumps(data, ensure_asciiFalse)。4.3 异常处理体系不是所有报错都要“解决掉”学习异常处理时很多人有一个错误认知try-except是用来“让程序不报错”的。这个理解非常危险。异常处理的目的不是掩盖错误而是让程序在遇到错误时有合理的应对方式。比如读取一个不存在的配置文件程序不应该直接崩溃也不应该假装没事继续跑而是应该给用户一个明确的提示或者启用默认配置。异常处理的完整结构长这样try: data read_data() process_data(data) except FileNotFoundError: print(数据文件不存在使用默认配置) except ValueError as e: print(f数据格式有问题{e}) else: print(处理成功) finally: print(无论成功失败这里的代码都会执行)这里每种句式都有明确用途except捕获指定异常类型多个except按从上到下的顺序匹配else只有在没有异常时执行finally无论有没有异常都会执行适合放释放资源的代码。我在实践中发现只捕获你能处理的异常类型很重要写一个裸except:会捕获包括KeyboardInterrupt在内的所有异常用户按CtrlC想终止程序都被吞掉这种代码很容易惹人生气。4.4 自定义异常与日志给错误留证据当需求变复杂后内置异常类型可能不够表达业务问题。比如读入一个成绩文件里面有一行数据格式不对你希望程序能明确说出是哪个字段错了。这时候自定义异常就很有用。class DataFormatError(Exception): pass def parse_score(line): parts line.split(,) if len(parts) ! 2: raise DataFormatError(f无效行: {line}) name, score parts try: score int(score) except ValueError: raise DataFormatError(f分数不是数字: {score}) return name, score自定义异常的用处是让错误语义更清楚。调用方看到DataFormatError就知道提取数据这一步出了问题而不是看到一个宽泛的ValueError还要猜。还有一个习惯我非常推荐用日志记录代替到处print。简单做法是使用标准库logging把错误信息写到文件里这样程序跑完之后还能回看问题记录而不是让信息被滚动终端冲掉。我一般只做两件事logging.basicConfig(filenameapp.log, levellogging.INFO)然后在异常发生时logging.exception(处理失败)异常栈就会完整保存下来。5. 面向对象入门让代码向真实业务靠拢5.1 学会类之前先搞清楚它解决什么问题很多初学者看到类就头疼一个很重要原因是大家先学了函数觉得函数已经能组织代码了为什么还要多一层类我的理解是函数组织的是“操作”类组织的是“数据 操作”的整体。以一个小学生成绩系统为例函数式的写法可能是save_score(name, score, class_name)、get_average(scores)、print_report(scores)数据和操作是分离的传参传得很累。面向对象则把“一个学生”定义成一个整体它有自己的名字、班级、分数也有自己的行为。这个转化其实是在模拟我们看待世界的方式。5.2 类和实例模板和实物的关系用生活类比类是“学生登记表”的模板实例是填好信息的一张具体登记表。构造函数__init__负责在创建实例时初始化它的状态。class Student: def __init__(self, name, class_name, score): self.name name self.class_name class_name self.score score def introduce(self): return f{self.class_name}的{self.name}分数{self.score} s Student(小明, 三年二班, 90) print(s.introduce())self是这个阶段最需要理解的概念。它指代当前正在操作的那个实例。调用Student(小明, 三年二班, 90)时Python会创建一个新对象然后把这个对象作为self传给__init__。如果看到def __init__(self, name)却疑惑这个self是哪来的我的建议是把它当作文法的一部分类里面定义方法第一个参数永远是实例本身名字叫self只是约定换成别的名字也能跑但没人会那么干。5.3 封装不是藏起来而是给外部一套干净的接口“封装”这个词听起来有点抽象其实它就是两件事内部细节你自己管外部只暴露需要的东西。Python没有真正的私有变量约定是用下划线表示“这是内部实现别乱动”。class BankAccount: def __init__(self, owner, balance): self.owner owner self._balance balance property def balance(self): return self._balance def deposit(self, amount): if amount 0: raise ValueError(存款金额必须大于0) self._balance amount return self.balance上面的代码里_balance被标成内部属性外部代码不应该直接修改它。想读取余额时通过balance这个属性想存钱时调用deposit方法。这样有什么好处好处是校验逻辑集中在一处如果以后要限制单次存款上限只需要改deposit方法内部外部所有调用方都不用动。property装饰器让一个方法能以属性的形式被访问这是一种非常优雅的接口设计。5.4 继承与多态复用公共部分替换差异部分继承解决的是“多个类有共同行为”的问题。比如“大学生”和“小学生”都是“学生”都有姓名和班级但自我介绍的方式可能不同。class Student: def __init__(self, name, class_name): self.name name self.class_name class_name def study(self): return f{self.name}正在学习 def introduce(self): return f我是{self.class_name}的{self.name} class CollegeStudent(Student): def __init__(self, name, class_name, major): super().__init__(name, class_name) self.major major def introduce(self): return f我是{self.major}专业的{self.name}子类通过super().__init__调用父类的初始化逻辑省去重复赋值。introduce在子类中被重新实现这就是“多态”的雏形同样是调用introduce方法不同对象给出的结果不同。这个思路在日常开发里非常常见比如日志处理器、文件导出器都可以通过继承抽象基类来统一接口。Python还有一个重要的动态特征叫“鸭子类型”不关心对象是什么类只关心它有没有某个方法。比如一个函数只需要调用obj.read()那么传入文件对象、字符串对象哪怕是一个自定义类的实例只要这个对象有read方法就行。这意味着Python的继承很多时候不是为了类型检查而是为了复用代码。5.5 特殊方法让自定义对象用起来像原生类型类里面那些双下划线开头和结尾的方法叫特殊方法。它们不是神秘的魔法而是让自定义对象参与Python语法操作的后门接口。class Score: def __init__(self, value): self.value value def __str__(self): return fScore({self.value}) def __eq__(self, other): if not isinstance(other, Score): return NotImplemented return self.value other.value def __gt__(self, other): return self.value other.value s1 Score(90) s2 Score(80) print(s1) # Score(90) print(s1 s2) # True print(s1 Score(90)) # True__str__定义了对象被print时显示的文本__eq__、__gt__定义了对象之间如何比较大小。学习特殊方法不需要一次记很多我的建议是先掌握__str__、__repr__、__eq__这几个因为它们直接关系到调试时能不能看清对象内容。这些方法就像一个协议你遵守了协议自定义对象就能无缝融入Python的各种语法和标准库。6. 综合实战与排错把这一阶段的知识串成一个工具6.1 动手项目做一个带存储的待办清单这个阶段我强烈建议做一个能把所有知识点串起来的小项目不需要多大但必须包含类、函数、文件读写、异常处理。我最推荐的是待办清单因为它领域清晰、扩展空间大。import json class TodoItem: def __init__(self, title, doneFalse): self.title title self.done done def to_dict(self): return {title: self.title, done: self.done} staticmethod def from_dict(data): return TodoItem(data[title], data[done]) class TodoList: def __init__(self, filenametodos.json): self.filename filename self.items [] self._load() def add(self, title): self.items.append(TodoItem(title)) self._save() def complete(self, index): if index 0 or index len(self.items): raise IndexError(待办序号超出范围) self.items[index].done True self._save() def _save(self): with open(self.filename, w, encodingutf-8) as f: json.dump([item.to_dict() for item in self.items], f, ensure_asciiFalse, indent2) def _load(self): try: with open(self.filename, r, encodingutf-8) as f: data json.load(f) self.items [TodoItem.from_dict(d) for d in data] except FileNotFoundError: self.items [] except json.JSONDecodeError: print(文件损坏初始化空清单) self.items []这个项目虽然简短但把所有关键点都覆盖了一遍TodoItem和TodoList体现类和对象to_dict和from_dict解决对象和字典的转换json.dump和json.load体现文件读写try/except处理文件不存在和JSON格式错误。再往后你可以加一个命令行交互入口用循环读取用户输入做一个真正能自己用的小工具。6.2 常见问题速查表这一阶段最容易踩的坑我在带人学习时发现下面这些问题几乎每个人都遇到过问题现象根本原因解决方法导入自己写的模块失败文件不在当前搜索路径检查所在目录或者把模块放在项目根目录下读文件报UnicodeDecodeError编码与文件实际编码不符明确指定正确的encoding参数写文件后内容没刷新没有关闭文件或用with使用上下文管理器确保文件关闭函数默认值是列表结果数据累积可变默认参数被共享用None作默认值函数内初始化字典取不存在的键报错直接使用dict[key]改用dict.get(key, default)异常被静默吞掉程序行为诡异裸except或空except捕获具体异常类型至少要有日志自定义对象print出来是内存地址未实现__str__实现__str__方法这张表建议直接存下来以后写代码遇到类似报错先回来查一遍。我认为学习阶段真正拉开差距的不是看了多少教程而是遇到问题后能不能快速定位并解决。6.3 如何让学习节奏平稳推进第三篇笔记覆盖的内容是100天计划里第一个“信息密集期”很容易学完后面忘前面。我自己的应对方法是三句话每天保持至少30行手写代码报错先读最后一行异常信息遇到不确定的语法优先翻官方文档而不是只搜代码片段。手写代码的意义在于让知识变成肌肉记忆。很多人看教程觉得都懂了一关上屏幕就写不出来原因是缺少“从零开始组织代码”的练习。报错读最后一行是因为Python异常信息已经告诉了你错误类型和出问题的文件与行号先看这个比翻整个控制台高效得多。翻官方文档则是培养判断力以免被一些过时、错误或者标题党的博客带偏。6.4 关于这一阶段最后的几句话回头看我自己的学习过程第三篇笔记对应的那段时间是最容易放弃的。函数、模块、数据结构、文件、异常、面向对象每一个都值得单独学但100天计划把它们压缩在很紧凑的节奏里压力是真实的。请记住节奏快并不代表你笨这个阶段的内容本来就需要反复回看。如果遇到某个知识点卡了两天我的建议是跳过去继续往下学过几天再回来看往往会突然明白。这种“先记问题再带着问题学新内容”的方式在那些连续且互相依赖的知识体系里比死磕一个点有效得多。
特别提醒:考试时间、报名批次等安排如有调整,以河南省应急管理厅及官方考点最新通知为准。

最新新闻

看完这篇文章,想了解更多?

报考条件、材料清单、最近批次,顾问一次帮你理清,别自己摸索。