Python期末复习全量笔记:切片、默认参数与C3算法高频考点解析 简介南开大学软件学院Python期末复习资料是一份精准覆盖核心考点的完整笔记面向软件学院本科生及Python进阶学习者系统梳理了数据结构、对象与引用、内存管理、面向对象编程等期末考试重点。压缩包内含1个docx文档5.28MB内容按知识点分块排列方便快速定位与背诵。目前已有1199人学习是经过验证的高热度复习资料。文档详细列出了字典、集合、元组、列表的异同与操作技巧包括切片写法、字典合并方法阐明了浅拷贝与深拷贝的区别及不可变对象场景对函数默认参数陷阱、*args与**kwargs、命名关键字参数做了示例说明还覆盖了魔法函数、装饰器、生成器、抽象类、正则表达式、三器一魔等进阶主题。特别是C3算法、多继承MRO顺序、钻石继承、类方法和静态方法、私有属性访问等易混淆点均有图解式讲解适合考前集中突击与反复查阅。1. Python 期末复习到底在考什么一份能当黑匣子用的全量笔记某高校软件学院的这份 Python 期末复习笔记最让我意外的是它没有按章节平铺而是把 C3 算法、魔法函数、可变默认参数这些最容易翻车的点全用代码例子顶了出来。整份笔记几乎没有废话每一行都是可以直接丢进解释器验证的片段。它适合两类人一类是刚学完一遍但还没做题的新手靠它把知识点连成网另一类是已经刷过题的熟手拿它做考前最后的边界核对。我把它拆开讲一遍重点放在“笔记里写了什么、为什么要这么写、考场上会怎么变着法子考”。2. 数据结构操作边界切片方向、setdefault、pop 与内存共享2.1 列表与字符串切片方向决定结果笔记里第一段高频考点就是切片。它给了一个字符串str lawedf然后让你看两个切片结果str lawedf print(str[-3:-1]) # 输出 ed print(str[-1:-4:-1]) # 输出 fde第一个str[-3:-1]是从倒数第 3 个字符取到倒数第 1 个字符之前。lawedf的倒数第 3 位是e倒数第 2 位是d倒数第 1 位是f但不包含所以结果是ed。第二个str[-1:-4:-1]是反向切片从末尾f开始往左走走到倒数第 4 位之前步长是 -1于是取了f、d、e三个字符输出fde。这里要记住两个规则切片区间永远是左闭右开不包含结束位步长为负数时起始位置必须在结束位置的右边否则取出来是空字符串。考试里最常见的变体是把步长换成正数然后让你猜会不会越界或者反过来写str[-1:-4]不带步长这时候由于起始位在结束位左边结果一定是空的。遇到这类题先画下标再下结论。2.2 字典的 setdefault、update、pop 与 popitem方法选型别混笔记里字典部分信息密度很高几乎每个方法都是一道判断题素材。先看生成字典dict1 dict.fromkeys(list, 10)fromkeys的语义是用一个序列里的元素当键全部给同一个默认值。注意这里如果键是可变对象直接会报类型错误因为字典的键必须是可哈希的。再往下看几个容易混的方法d {5: 2} d.setdefault(3, None) print(d) # {5: 2, 3: None} d1 {a: 1} d1.update(d) # 用 d 的内容合并进 d1 print(d1) # {a: 1, 5: 2, 3: None}setdefault(key, default)的行为是如果 key 不存在就把 key 和 default 加进去如果 key 已经存在啥也不改。这里有个细节很多新手会判断错——setdefault是“会写入”的而get(key, default)只是“读的时候给个默认值”不会往字典里写入新键。笔记里特意强调“get 若是没这个 key 是不会往 dict 里加没有的那个元素的”这句话就是考点。删除相关的也要分清楚del d[key]是彻底把键值对从字典里移除。d.clear()是清空字典字典对象还在只是里面没内容了。d.pop(key)必须传 key删掉指定键并返回它的值。d.popitem()不传参删掉字典的“最后一项”。列表的pop()可以无参删除最后一个元素但字典的pop()不行这就是笔记里强调“key 不能不给出”的原因。2.3 元组、集合与空集合不可变与无序的边界笔记里有一个很经典的表述字典和集合是无序的因为它们没法用下标索引元组、数字、字符串是不可变的集合中不允许出现重复元素。这些话看起来简单但组合起来能出很多判断对错的题。t (1,) # 这是元组必须带逗号 empty_set set() # 正确创建空集合 empty_dict {} # 这是空字典不是空集合创建只有一个元素的元组时要加逗号不加逗号(1)就只是数字 1 外面套了一层括号类型是 int。创建空集合只能用set()用{}默认建出来的是空字典。这是试卷上出现率极高的一个坑。集合用add()加元素元素不能重复而且集合本身是无序的所以 1 和 True 在集合里会被当成同一个元素——因为1 True且它们的哈希值相同。这个点如果笔记里没有展开考场上遇到判断题就得自己补上。还有列表与字典的对比笔记里给了三条字典的查找和搜索速度不会随 key 数量增加而变化查找速度很快但字典占用内存更多。原因是字典底层是哈希表理想情况下查找是 O(1)而列表是顺序扫描元素多了以后速度明显变慢。考试问你“为什么不用列表当字典用”或者“字典为什么快”就答这三条。2.4 内存共享列表存的是引用不是值笔记里有个图和一句话列表不直接存具体的值在内存里只存对象的 ID两个变量指向同一个列表时它们的 ID 一样改其中一个另一个也跟着变。a [1, 2, 3] b a # b 和 a 指向同一个列表对象 b.append(4) print(a) # [1, 2, 3, 4]这解释了为什么“直接等于”不是拷贝。同样道理元组里的元素如果是列表元组本身不可变但列表里的内容照样可以被修改。面试和期末都喜欢拿这个出“光看代码猜输出”的题。3. 函数进阶默认参数陷阱与 *args/**kwargs 的解包细节3.1 可变默认参数是经典翻车点笔记里花了大篇幅讲默认参数原文说得很直白L 是可变参数第一次调用后默认值的内容被改变了下次再调用拿到的就不是函数定义时的[]了。看这段最经典的错误用法def add_item(item, L[]): L.append(item) return L print(add_item(1)) # [1] print(add_item(2)) # [1, 2]你以为是 [2]原因是L[]这个默认值在函数定义时只创建一次之后所有不传 L 的调用都共享同一个列表。正确的做法是用不可变对象占位def add_item(item, LNone): if L is None: L [] L.append(item) return L记住笔记里那句话默认参数必须指向不变对象。None、数字、字符串、元组都可以当默认参数列表、字典、集合都不行。考试变体通常是让你判断“第二次调用时打印什么”或者把换成L()和Lset()让你分析。3.2 *args 与 **kwargs定义侧与调用侧的两种形态笔记把*args和**kwargs拆成了定义和调用两层来讲这个方法在考场上特别有用。定义侧def collect(*args, **kwargs): # args 会组装成一个元组 # kwargs 会组装成一个字典 print(args) print(kwargs) collect(1, 2, 3, namepy, level3) # (1, 2, 3) # {name: py, level: 3}调用侧def add(a, b, c): return a b c t (1, 2, 3) print(add(*t)) # 解包元组等价于 add(1, 2, 3) d {a: 1, b: 2, c: 3} print(add(**d)) # 解包字典等价于 add(a1, b2, c3)整理成一句话函数定义时*args收集多余的位置参数成元组**kwargs收集多余的关键字参数成字典函数调用时*把元组/列表拆开作为位置参数传入**把字典拆开作为关键字参数传入。两者同时存在时必须*args在前、**kwargs在后否则语法直接报错。还有一个实用技巧是配合字典调用函数。笔记里提到“字典里还可以调用函数”指的是可以用字典把一组函数存起来再通过键去取def add(a, b): return a b def mul(a, b): return a * b funcs {add: add, mul: mul} print(funcs[add](2, 3)) # 5 print(funcs[mul](2, 3)) # 6这在写菜单程序、命令分发的时候非常常见期末填空填“字典的 value 可以存函数引用”的概率不低。3.3 多返回值其实是一个元组笔记里有一句总结函数有且只有一个返回值。Python 里写return a, b看着像返回两个值实际上是把它们打包成了一个元组返回。所以def split_point(): return 1, 2 result split_point() print(type(result)) # class tuple这个点在解包赋值里也有体现x, y split_point()本质上是用元组解包把两个元素分别赋给两个变量。写代码的时候没问题但判断题如果问“函数能不能返回多个值”答案是“不能只能返回一个元组”。3.4 装饰器一个返回函数的函数笔记里正好有一个计时器例子也是装饰器的入门套路import time def timeit(func): def wrapper(*args, **kwargs): start time.time() result func(*args, **kwargs) print(func.__name__, time.time() - start) return result return wrapper def foo(): time.sleep(0.1) return done foo timeit(foo) print(foo())这段代码结合了上一节的*args和**kwargswrapper内部用它们原样转发参数这样计时器就能装饰任何签名的函数。装饰器本质上是“给函数 return 一个封装好了的新函数”timeit语法糖等价于手动执行foo timeit(foo)。笔记里还提到类方法和静态方法。classmethod装饰的第一个形参是类对象习惯叫cls通常用来访问类属性staticmethod不需要传实例也不需要传类。这两个和property一样本质都是装饰器只是作用目标不同。考试常见考法就是给你一段类代码问哪个方法可以用类直接调用、哪个方法能拿实例调用、谁不能访问实例属性。4. 多重继承与 MROC3 算法不玄学手算一遍就懂4.1 Python 3 只剩 C3经典算法已成往事笔记里列了三条 MRO 算法经典方式、Python 2.2 新式算法、Python 2.3 新式算法C3。Python 3 里只保留最后一种也就是 C3 算法。考场上只要题目用的是 Python 3所有多继承的方法查找顺序都由 C3 决定不需要再考虑旧算法。C3 的核心目标是解决两个问题一是子类永远在父类之前二是同一个父类在 MRO 里只出现一次。Python 官方库里的大量混入类mixin设计都依赖这个顺序如果顺序错了同名方法被谁覆盖就成了玄学。4.2 手算 C3两条原则加一个消去步骤笔记给了一串继承遍历结果D、C1、C2、B1、A1、C3、B2、B3、A2、A3、Obj。这不是随便排出来的手算时记住两条原则从子类开始优先走左边的父类分支当一个类可以通过多个路径到达时只保留一次并且在它依赖的子类全部输出后才能输出。实际操作可以这样先写出从 D 出发按“深度优先、从左到右”遍历出来的顺序然后从头到尾扫描遇到重复出现的类就把它后面那次删掉。笔记里的“一旦左边有没有子节点的就继续剪掉这个节点”说的就是深度优先走到头的过程。Obj 永远在最后因为它是所有新式类的根。验证手算结果有个更直接的手段打开解释器直接看类名.__mro__class A1: pass class B1(A1): pass class C1(B1): pass class A2: pass class B2(A2): pass class C2(B2): pass class D(C1, C2): pass print(D.__mro__)输出的元组顺序就是最权威的答案。考试时如果题目允许先在脑内还原继承关系图再按“从左分支往右分支走、重复的类第一次出现后就不再出现”去排比硬记公式稳。4.3 super() 在钻石继承里只走一次笔记里有一个重要的点多继承里如果直接用父类名字调__init__会出现多次初始化用super().__init__()则按 MRO 顺序查找一个类只被初始化一次。class A: def __init__(self): print(A) class M1(A): def __init__(self): print(M1) super().__init__() class M2(A): def __init__(self): print(M2) super().__init__() class D(M1, M2): def __init__(self): print(D) super().__init__() d D()这段代码会依次打印 D、M1、M2、AA 只被初始化一次。如果改成M1.__init__(self)和M2.__init__(self)那种写法A 的__init__会被执行两次。期末考这道题的概率很大因为它考的既是 C3 顺序又是 super 的实现机制。4.4 继承顺序写反的翻车场景笔记里举了一个继承顺序写反导致方法找不到的例子。父类 Animal 更高级派生类应该把更基础的类写在左侧。比如class TracDog(Dog, Animal)这种写法如果导致 Animal 的方法优先级低于 Dog 的同名方法而预期是 Animal 的版本生效输出就会和设计意图相反。实际排查时先看__mro__再定位同名方法定义在哪一层。如果子类没有覆盖某个方法Python 从左到右找第一个能用的这个顺序一旦被误解代码行为就像黑匣子。所以遇到多继承第一件事就是把类名.__mro__打印出来看一眼比猜快得多。5. 魔法函数与高频踩坑迭代器、with、私有属性这样排查5.1iter与getitemfor 循环的两种协议笔记里说得很清楚如果一个类要被for ... in循环使用就必须实现__iter__()该方法返回一个迭代对象Python 的 for 循环不断调用迭代对象的next()方法取值直到遇到StopIteration退出。还有一个冷门知识点如果拿不到迭代器也就是没实现__iter__Python 解释器会回头找__getitem__方法直到整个对象取完。这个特性让某些老式写法也能被 for 遍历class SimpleList: def __init__(self, items): self.items items def __getitem__(self, index): return self.items[index] s SimpleList([10, 20, 30]) for value in s: print(value)这段代码没有__iter__但 for 循环能跑因为解释器发现可以按下标 0、1、2 顺序取越界抛出IndexError后自动结束。题型常考“以下类能不能被 for 遍历”答案就是看有没有__iter__没有就看有没有__getitem__。5.2enter与exitwith 背后的两句话笔记里用“看起来充满魔法但不仅仅是魔法”来形容 with。它的机制其实只有两条紧跟 with 的对象必须提供__enter__()和__exit__()方法进入时__enter__()的返回值赋给 as 后面的变量代码块执行完后自动调用__exit__()即使出异常也会调用。class ManagedFile: def __enter__(self): print(open) return self def __exit__(self, exc_type, exc_val, exc_tb): print(close) with ManagedFile() as f: print(working)输出顺序是 open、working、close。期末如果考 with多半是问两件事as 后面的变量来自哪个方法的返回值以及代码块抛异常后__exit__是否还会执行。答案是“来自__enter__的返回值”和“会执行”。5.3str与reprprint 先调哪个笔记里说 print 函数会先调用str()去转字符串而str()会调用对象类里的__str__方法如果类里没实现__str__系统会找__repr__来模拟。所以两个方法都写时优先走__str__只写__repr__也能让 print 正常工作。调试的时候类里至少写一个__repr__是划算的这样不管 print 还是直接看交互式输出都能看到可读信息。5.4 私有属性不是真私有name mangling 边界笔记里有一句话很实在Python 假定没有恶意行为但留出了类外访问私有属性和方法的方式。双下划线开头的属性会被解释器改名成_类名__属性名外部用这个改过的名字照样能访问。class Person: def __init__(self): self.__age 20 p Person() print(p._Person__age) # 20期末考私有属性最常见的题就是p.__age能不能访问、报什么错。答案是能访问但需要写成p._Person__age。这种设计不是为了阻止访问只是防止无意覆盖。5.5 深浅拷贝的两个最大误解笔记里对比了直接赋值、浅拷贝、深拷贝三种情况。直接等号不产生新对象两个变量 ID 一样浅拷贝创建新的最外层对象但里面的嵌套对象还是引用原来的深拷贝连同嵌套对象全部复制。import copy a [[1, 2], [3, 4]] b copy.copy(a) # 浅拷贝 c copy.deepcopy(a) # 深拷贝 print(a[0] is b[0]) # True嵌套列表还是同一个 print(a[0] is c[0]) # False嵌套列表也被复制了这里还有个容易忘的结论当最外层对象是不可变类型时浅拷贝得到的对象指向原对象的内存空间只有最外层是可变类型时浅拷贝才会生成新对象。判断深浅拷贝的题先看最外层是否可变再看嵌套内容是否为可变两步就可以锁定答案。5.6 三条高频翻车记录第一条想创建空集合写了s {}结果得到的是空字典。现象是后续调用s.add(1)直接报 AttributeError。原因是{}在 Python 里永远表示空字典。解决空集合必须用set()。第二条函数里用了可变默认参数L[]第二次调用时第一次的数据还在。现象是add_item(1)返回[1]add_item(2)返回[1, 2]。原因是默认参数在定义时只创建一次后续调用共享同一对象。解决默认参数写成None函数内部再初始化。第三条浅拷贝后修改嵌套列表原列表跟着变。现象是改了b[0].append(99)a里也出现了 99。原因是浅拷贝只复制了最外层的引用结构嵌套列表没有复制。解决需要完全独立的数据时用copy.deepcopy()考试写代码题时一定要看清题目要求的是“值独立”还是“浅层独立”。6. 期末当天的 90 分钟自查把这份笔记变成可执行清单最后讲一个我一直在用的复习流程拿到一份知识点密集的笔记不要从头再读一遍而是“看结论、反推例子、构造变体”。具体分三步走。第一步挑出笔记里所有带代码片段的知识点把它们当作判断题遮住输出结果自己在解释器里跑一遍跑之前先写下预测答案。切片方向、setdefault是否写入、默认参数累积、__mro__顺序、浅拷贝的嵌套 id这些全部是适合预测的考点。预测错了就用红笔标住因为“看懂了”和“能预测对”是两回事。第二步对每个标住的知识点构造一个变体。比如原题是str[-3:-1]变体就把步长改成 -1再改成 2看输出变不变原题是D(C1, C2)多继承变体就把父类顺序对调打印__mro__对比差异。这一步花的时间最多但考试里最拿分的就是这些“看着眼熟、稍微一变就不会”的题。第三步考前一个小时只做一件事把每章的翻车记录过一遍。集合用set()创建、默认参数不用可变对象、私有属性改名规则、with块的__exit__一定执行、浅拷贝不嵌套、super()按 MRO 走一次——每一条对着代码快速盯一遍相当于给大脑做一层索引。我自己的血泪教训是有一年复习到深夜把所有代码都跑了三遍觉得稳了结果第二天一道装饰器叠加的题直接把我打回原形。问题就出在我只验证了装饰器本身从没想过多个装饰器叠加时执行顺序会反转。从那以后我每次都强制走一遍“预测 → 变体 → 翻车记录”的三轮自查再也没在期末翻过车。希望帮到你。本文还有配套的精品资源点击获取