ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python核心概念解析:可变性、无序性与对象模型

Python核心概念解析:可变性、无序性与对象模型

简介:本资源是南开大学软件学院Python课程期末复习的系统性整理笔记,面向计算机相关专业本科生及Python初学者,聚焦核心考点与易错难点,助力高效备考与知识体系构建。内容覆盖数据结构(字典、集合、元组、列表的特性与操作)、内存管理(引用机制、浅拷贝与深拷贝)、面向对象编程(类与实例属性、私有成员、@classmethod/@staticmethod装饰器、C3算法下的MRO继承顺序)、魔法函数(init、str、__dict__等)、函数进阶(*args/**kwargs、默认参数陷阱、生成器与三器一魔)以及正则表达式等关键模块,知识点讲解结合典型代码示例与运行结果分析。资源为1个5.28MB的DOCX文档,结构清晰、排版规范,含大量对比表格、内存图示与语法速查片段,便于复习查阅与重点标注。目前已有1200人学习下载,是兼顾理论深度与应试实用性的高质量期末冲刺资料。

1. Python期末复习不是背题库,而是搞懂“Python怎么想问题”:一份南开软院学生亲测有效的入门路径

你是不是也这样:翻开《Python期末复习(超全)》这个标题,第一反应是——“又来一套题?背完就忘,考完就丢”?别急,这份材料根本不是传统意义上的“题库”,它是一份用真实课堂节奏打磨出来的思维导图式复习指南。它不从“print('Hello World')”开始教,而是直接带你站在南开软件学院老师出题的视角看:为什么字典和集合必须无序?为什么元组能当字典的key而列表不能?为什么list.sort()没有返回值,但sorted()却有?这些问题背后,不是规则,而是Python对内存、对象、引用的真实处理逻辑。它适合谁?适合那个刚学完循环和if语句、一看到“MRO算法”就头皮发麻、抄了十遍super()还是不知道它在找谁的你;也适合那个已经会写爬虫但总被面试官问“a = [1,2]; b = a; b.append(3)之后a变没变”给问懵的新手。这不是让你“多做一道题”,而是帮你把散落的知识点,焊进一个叫“Python世界观”的底座里——底座稳了,新代码来了,你第一反应不再是“这行报错我搜过但看不懂”,而是“哦,它在找这个对象的__enter__方法”。


2. 先搞懂一个概念:Python里“无序”不是乱序,而是“不靠位置说话”:字典与集合的本质区别

很多新手卡在第一步:为什么老师反复强调“字典和集合是无序的”,可我print(dict)明明看到键值对排得整整齐齐?这里藏着一个关键误会——“无序”不是指打印出来不整齐,而是指Python不保证、也不依赖元素的物理存储顺序来定义它们的身份和行为。我们拆开来看。

2.1 字典:靠“键”查,不靠“第几个”

想象你有一本纸质通讯录,里面按人名拼音排序。你找“张三”,翻到P页;找“李四”,翻到L页。你从不关心“张三”是第37页、“李四”是第21页——你只认名字。字典就是这本通讯录的电子版。它的核心能力是O(1)平均时间复杂度的查找,靠的是哈希表(Hash Table):当你写my_dict['name'],Python立刻把'name'算出一个数字(哈希值),跳到内存里对应的位置取值,完全跳过了“从头数第几个”的过程。所以,哪怕你先my_dict['z'] = 100再my_dict['a'] = 1,它内部存储顺序可能反着来,但你调用my_dict['a']永远秒出结果。这就是“无序”的价值:牺牲顺序,换取速度。

# 看似有序,实则偶然 student_grades = {'math': 85, 'english': 92, 'science': 78} print(student_grades) # 可能输出 {'math': 85, 'english': 92, 'science': 78} # 但这顺序不是语言保证的!Python 3.7+虽保持插入顺序(为兼容性加的优化),但逻辑上它仍是“无序结构” # 你绝不能写:first_subject = list(student_grades.keys())[0] // 这是危险的依赖!

提示:list(dict.keys())得到的列表顺序,在Python 3.7+中确实是插入顺序,但这属于实现细节,不是语言规范。考试或面试中若考察“字典是否有序”,标准答案仍是“逻辑上无序”,因为它的设计目标从来不是维护顺序,而是高效查找。

2.2 集合:去重的终极形态,连“位置”都不给你留

集合更进一步。它连“键”都不需要,只要求元素唯一且可哈希。它的底层也是哈希表,但只存“键”,没有“值”。所以,当你写my_set = {1, 2, 3},Python做的不是“记住1在前、2在中、3在后”,而是“记住内存里有一个位置存着1,一个位置存着2,一个位置存着3”。你无法用索引my_set[0]访问,因为它压根没定义“第0个”是谁。它的所有操作——add(),remove(),in判断——都基于哈希查找,时间复杂度同样是O(1)。

# 创建空集合:必须用 set(),{} 是字典!这是新手高频翻车点 empty_set = set() # ✅ 正确:创建空集合 empty_dict = {} # ✅ 正确:创建空字典 # wrong_set = {} # ❌ 错误:这创建的是字典,不是集合! # 集合自动去重,且不记录顺序 nums = [1, 2, 2, 3, 1, 4] unique_nums = set(nums) print(unique_nums) # 输出可能是 {1, 2, 3, 4} 或 {4, 1, 2, 3},顺序不重要! # 检查是否存在?快如闪电 print(2 in unique_nums) # True,O(1)时间

2.3 对比列表:为什么列表能索引,而字典/集合不能?

列表是“数组”的Python化身。它在内存里是一块连续的空间,每个位置存一个元素的引用(ID)。所以my_list[0]就是“去内存地址A取第一个引用”,my_list[1]就是“去地址A+8字节取第二个引用”。这种结构天生支持索引和切片,但插入/删除中间元素要移动后面所有元素,时间复杂度O(n)。

特性列表 (list)字典 (dict)集合 (set)
是否有序有序(位置即身份)逻辑无序(键即身份)逻辑无序(元素即身份)
是否可索引✅list[0]❌dict[0]报错❌set[0]报错
是否可重复✅ 允许重复元素❌ key不能重复(值可重复)❌ 元素绝对唯一
核心优势快速按位置访问、切片快速按键查找、插入、删除快速去重、成员判断、集合运算

2.4 常见问题排查:为什么我的字典“看起来有序”却报错?

现象1:我用for k in my_dict:遍历,输出顺序和我插入的一样,但老师说字典无序,我该信谁?
原因:Python 3.7+为提升用户体验,默认保持插入顺序(CPython实现细节),但这不改变其“无序数据结构”的本质定义。考试中若问“字典是否有序”,答“逻辑上无序”才得分。依赖顺序写代码(如取list(dict.keys())[0])在旧版本Python或某些解释器下会崩溃。
解决:所有需要确定顺序的场景,显式转换:sorted(dict.keys())(按字母序)或list(dict.items())(按插入序,仅限3.7+且不推荐依赖)。

现象2:my_set = {1, 2, 3}; print(my_set[0])报错TypeError: 'set' object is not subscriptable。
原因:集合不支持索引操作符[],因为它没有“第0个”的概念。它的设计哲学是“只问‘有没有’,不问‘在哪儿’”。
解决:若需按顺序处理,先转成列表:for item in sorted(my_set):或list(my_set)[0](但后者顺序不保证,慎用)。

现象3:my_dict = {'a': 1, 'b': 2}; my_dict['c']报错KeyError,但my_dict.get('c')返回None。
原因:[]是“强硬访问”,找不到就炸;.get()是“礼貌询问”,找不到就给个默认答案(默认None)。这是Python“请求宽恕而非许可”(EAFP)哲学的体现。
解决:处理可能不存在的key时,优先用.get(key, default_value),避免try/except过度使用。

现象4:my_list = [1, 2, 3]; my_list.append([4, 5])后,my_list变成[1, 2, 3, [4, 5]],但我想把4和5“加进去”而不是“加一个列表”。
原因:.append()是“加一个元素”,.extend()才是“加多个元素”。新手常混淆这两个方法。
解决:记口诀:“append是单个,extend是展开”。要加多个,用my_list.extend([4, 5]),结果是[1, 2, 3, 4, 5]。


3. 别再死记硬背了:理解“可变”与“不可变”,才能真正看懂Python的内存

“元组不可变,列表可变”——这句话你背过,但当a = (1, 2); b = a; b += (3,)后,a还是(1, 2),而a = [1, 2]; b = a; b += [3]后,a却变成了[1, 2, 3],你是不是瞬间懵了?这背后不是语法玄学,而是Python对对象身份(identity)和对象值(value)的严格区分。搞懂这个,你才算真正踏入Python的大门。

3.1 一切皆对象:三个身份证,缺一不可

Python里,每个东西都是对象,它有三张“身份证”:

  • 标识(identity):对象在内存中的唯一地址,用id()函数查看。就像你的身份证号,一生不变。
  • 类型(type):对象的种类,用type()查看。比如int,str,list。
  • 值(value):对象当前携带的数据。比如数字5,字符串"hello"。

关键来了:变量(如a,b)不是盒子,而是贴在对象身上的标签。a = 5的意思是“把标签a贴到内存里那个值为5的对象上”。b = a的意思是“把另一个标签b,也贴到同一个对象上”。所以a和b指向同一个id。

# 不可变对象:数字 x = 10 y = x print(id(x) == id(y)) # True,x和y指向同一个整数对象 x = x + 1 # 注意!这不是修改10,而是让x指向一个新的对象:11 print(x) # 11 print(y) # 10,y依然指着原来的10 print(id(x) == id(y)) # False,现在x和y指向不同对象了

3.2 可变对象:标签贴在“容器”上,容器里的东西可以换

列表、字典、集合是“容器型”对象。a = [1, 2]的意思是:在内存里造一个“箱子”(列表对象),把1和2放进去,再把标签a贴在这个箱子上。b = a是把另一个标签b也贴到同一个箱子上。此时,通过a或b去修改箱子里的东西(比如a.append(3)),箱子本身没换,只是里面多了个3,所以b看到的也是[1, 2, 3]。

# 可变对象:列表 a = [1, 2] b = a # b和a指向同一个列表对象 print(id(a) == id(b)) # True a.append(3) # 修改箱子:往里面加一个3 print(a) # [1, 2, 3] print(b) # [1, 2, 3],b看到的也是修改后的箱子! # 关键对比:+= 对可变对象是原地修改,对不可变对象是创建新对象 c = [1, 2] d = c c += [3] # 等价于 c.extend([3]),原地修改 print(c) # [1, 2, 3] print(d) # [1, 2, 3],d也变了 e = (1, 2) f = e e += (3,) # 等价于 e = e + (3,),创建新元组 print(e) # (1, 2, 3) print(f) # (1, 2),f没变!

3.3 元组的“不可变”到底指什么?为什么它能当字典key?

元组的不可变,是指元组对象自身包含的元素引用不能被修改。你不能做my_tuple[0] = 10。但它不保证它里面的元素本身不可变!如果元组里存了一个列表,那个列表还是可以变的。

# 元组的不可变是“一层”的 t = (1, [2, 3], 'hello') # t[0] = 10 # ❌ 报错!不能改元组的第一个元素 # t[2] = 'world' # ❌ 报错!不能改元组的第三个元素 t[1].append(4) # ✅ 可以!改的是元组里那个列表对象,元组只是存了它的引用 print(t) # (1, [2, 3, 4], 'hello') # 为什么能当key?因为字典的key必须是“可哈希的”(hashable) # 可哈希意味着:对象的hash值在其生命周期内不变,且相等的对象必须有相同hash值 # 不可变对象(str, int, tuple)天然满足;可变对象(list, dict, set)不满足,因为值变了hash就得变 my_dict = {(1, 2): "point", ('a', 'b'): "pair"} # ✅ 元组可哈希,能当key # my_dict = {[1, 2]: "point"} # ❌ 列表不可哈希,报错 TypeError

3.4 常见问题排查:为什么我的函数默认参数“记住了”上次的值?

现象:写了一个函数def add_item(item, my_list=[]): my_list.append(item); return my_list,第一次调用add_item(1)返回[1],第二次调用add_item(2)却返回[1, 2]!
原因:默认参数my_list=[]是在函数定义时创建的,不是每次调用时创建。这个空列表对象在内存里只有一份,所有不传my_list参数的调用,都共享这个列表对象。这就是“可变默认参数陷阱”。
解决:永远用None作为默认值,然后在函数体内检查并创建新对象:

# ❌ 危险!可变默认参数 def bad_append(item, lst=[]): lst.append(item) return lst # ✅ 安全!用None作哨兵 def good_append(item, lst=None): if lst is None: lst = [] # 每次调用都创建新列表 lst.append(item) return lst print(good_append(1)) # [1] print(good_append(2)) # [2],干净!

现象:a = [1, 2]; b = a; b[0] = 99; print(a)输出[99, 2],我以为b = a是复制了一份。
原因:=是赋值,不是复制。它只是让b也指向a所指向的那个列表对象。要真正复制,得用copy()(浅拷贝)或deepcopy()(深拷贝)。
解决:明确你的需求:

  • 只需一层复制(列表里全是不可变对象):b = a.copy()或b = a[:]
  • 需要完全独立副本(列表里还有列表):import copy; b = copy.deepcopy(a)

4. 面向对象不是炫技,是让代码“自己管自己”:从self到super()的实战逻辑

“面向对象编程”听起来高大上,但在Python期末复习里,它最朴素的目标就一个:把数据(属性)和操作数据的代码(方法)打包在一起,让这个包(对象)能自己管理自己的状态。self不是语法糖,它是这个包的“内部身份证”;super()不是魔法,它是这个包在家族谱系里“向上找长辈帮忙”的联络方式。我们从一个具体例子出发。

4.1self:对象的“我”,不是关键字,是约定

self只是一个形参名,你可以叫它this或me,但所有人都叫self,这是约定。它的作用,是让类里的方法能明确知道:“我现在操作的是哪个具体的实例?” 没有self,方法就不知道自己在跟谁对话。

class Student: def __init__(self, name, score): self.name = name # 把参数name存到这个实例的name属性里 self.score = score # 把参数score存到这个实例的score属性里 def introduce(self): # self.name 就是“我自己的名字”,不是某个全局变量 return f"我是{self.name},分数是{self.score}" def upgrade(self, points): # self.score 就是“我自己的分数”,升级只影响我自己 self.score += points return f"{self.name}升级了!新分数:{self.score}" # 创建两个不同的实例(两个不同的学生) alice = Student("Alice", 85) bob = Student("Bob", 92) print(alice.introduce()) # 我是Alice,分数是85 print(bob.introduce()) # 我是Bob,分数是92 alice.upgrade(5) # Alice升级了!新分数:90 print(bob.score) # 92,Bob的分数完全没受影响!

4.2 继承:不是“抄代码”,是“建立家族关系”

继承的核心,是让子类自动获得父类的所有属性和方法,并可以在此基础上添加新功能或修改旧功能。它建立的是一种“is-a”(是一个)关系:Dogis-aAnimal。南开复习材料里提到的“钻石继承”和“MRO算法”,就是当这个家族关系变得复杂(比如Dog同时继承Animal和Pet)时,Python如何确保每个方法只被调用一次、且按正确顺序调用的规则。

class Animal: def __init__(self, name): self.name = name def speak(self): return f"{self.name} makes a sound" class Dog(Animal): # Dog 继承 Animal def speak(self): # 重写(override)父类方法 return f"{self.name} barks: Woof!" class Cat(Animal): def speak(self): return f"{self.name} meows: Meow!" # Dog和Cat自动有了__init__和name属性,无需重复写 dog = Dog("Buddy") cat = Cat("Whiskers") print(dog.speak()) # Buddy barks: Woof! print(cat.speak()) # Whiskers meows: Meow!

4.3super():家族里的“找长辈”协议,解决钻石继承难题

当继承链变长,比如class Medium1(Father, Mother):,而Father和Mother又都继承自Grandparent,就可能出现“Grandparent.__init__()被调用两次”的问题(钻石继承)。super()就是Python提供的解决方案:它不指定具体调哪个父类,而是按照方法解析顺序(MRO),去找下一个应该执行的类的方法。

class Grandparent: def __init__(self, name): print(f"Grandparent.__init__({name})") self.name = name class Father(Grandparent): def __init__(self, name, job): print(f"Father.__init__({name}, {job})") super().__init__(name) # 调用MRO中下一个类(Grandparent)的__init__ self.job = job class Mother(Grandparent): def __init__(self, name, hobby): print(f"Mother.__init__({name}, {hobby})") super().__init__(name) # 同样调用Grandparent.__init__ self.hobby = hobby class Child(Father, Mother): def __init__(self, name, job, hobby, school): print(f"Child.__init__({name}, {job}, {hobby}, {school})") # super()会按MRO顺序调用:Father.__init__ -> Mother.__init__ -> Grandparent.__init__ # Grandparent.__init__只执行一次! super().__init__(name, job) # 先调Father.__init__ # 注意:Mother.__init__不会被自动调用,需要手动补上(或在Father.__init__里用super调用) # 这里简化,实际复杂继承需仔细设计 self.school = school # 查看MRO顺序 print(Child.__mro__) # 输出类似:(<class '__main__.Child'>, <class '__main__.Father'>, <class '__main__.Mother'>, <class '__main__.Grandparent'>, <class 'object'>) # Python 3 使用C3线性化算法计算MRO,确保每个父类只出现一次,且子类总在父类之前

4.4 常见问题排查:为什么__init__里不写super(),我的父类属性就没了?

现象:class Dog(Animal): def __init__(self, name, breed): self.breed = breed,然后dog = Dog("Buddy", "Golden"),print(dog.name)报错AttributeError。
原因:你重写了__init__,但没调用父类Animal.__init__,所以self.name = name这行根本没执行,name属性从未被创建。
解决:只要重写了__init__,就必须显式调用super().__init__(...)来初始化父类部分。

现象:class A: def method(self): print("A"); class B(A): def method(self): print("B"); class C(A): def method(self): print("C"); class D(B, C): pass; D().method()输出"B",为什么不是"C"或报错?
原因:这就是MRO在起作用。D.__mro__是(D, B, C, A, object),所以method首先在D找(没找到),再到B找(找到了),就执行B.method,不再继续往下找C。
解决:理解MRO是调试多继承的关键。用ClassName.__mro__随时查看顺序。

现象:@property装饰器是什么?为什么song.length能像属性一样访问,但song.set_length(100)却要调用方法?
原因:@property把一个方法伪装成了属性。song.length触发的是length这个@property方法,它内部可以做任何逻辑(比如检查合法性),但调用者感觉不到。@length.setter则定义了song.length = 100时该做什么。
解决:这是封装的利器。把数据校验、计算逻辑藏在属性访问背后,对外提供简洁接口。

class Song: def __init__(self, title): self.title = title self._length = 0 # 真实存储用私有属性 @property def length(self): """getter:读取length""" return self._length @length.setter def length(self, value): """setter:设置length,带校验""" if value < 0: raise ValueError("Length cannot be negative") self._length = value song = Song("My Song") song.length = 180 # 触发 setter,自动校验 print(song.length) # 触发 getter,返回180

5. 从“能跑通”到“真理解”:动手跑通第一个面向对象小项目——学生成绩管理系统

光看概念容易飘,现在我们动手做一个极简但完整的项目:一个能录入、查询、统计学生成绩的系统。它会用到复习材料里所有的核心概念:类、属性、方法、继承、super()、@property、异常处理。每一步都解释清楚“为什么这么写”,而不是“照着抄”。

5.1 第一步:定义基础类Student,封装数据和基本行为

我们要让每个学生对象,自己记住自己的名字、各科成绩,并能算出自己的平均分。这正是self和属性的用武之地。

class Student: def __init__(self, name, scores=None): """ 初始化学生 :param name: 学生姓名(str) :param scores: 成绩字典,如 {'math': 85, 'english': 92},默认为空字典 """ self.name = name # 用None作默认值,避免可变默认参数陷阱 self._scores = scores if scores is not None else {} @property def scores(self): """getter:返回成绩字典的副本,防止外部直接修改""" return self._scores.copy() # 返回副本,保护内部数据 def add_score(self, subject, score): """添加或更新一门课的成绩""" if not isinstance(score, (int, float)) or score < 0 or score > 100: raise ValueError("Score must be a number between 0 and 100") self._scores[subject] = score def get_average(self): """计算并返回平均分""" if not self._scores: return 0.0 return sum(self._scores.values()) / len(self._scores) def __str__(self): """定义打印学生时的友好格式""" avg = self.get_average() return f"学生: {self.name}, 成绩: {self._scores}, 平均分: {avg:.1f}"

解释:这里用了@property保护scores,外部只能读不能改;add_score里做了输入校验;__str__让print(student)输出可读信息。这些都是面向对象封装的体现。

5.2 第二步:定义管理类GradeManager,用组合代替继承

复习材料提到“组合就是没有继承但是用了人家的实例”。GradeManager不需要是Student的子类,它只需要“拥有”一堆Student实例来管理。这就是组合(Composition),比继承(Inheritance)更灵活、更符合实际。

class GradeManager: def __init__(self): self._students = {} # 用字典存学生,key是姓名,value是Student对象 def add_student(self, name): """添加一个新学生""" if name in self._students: raise ValueError(f"学生 {name} 已存在") self._students[name] = Student(name) def add_score(self, name, subject, score): """给指定学生添加成绩""" if name not in self._students: raise ValueError(f"学生 {name} 不存在") self._students[name].add_score(subject, score) def get_student_average(self, name): """获取指定学生的平均分""" if name not in self._students: raise ValueError(f"学生 {name} 不存在") return self._students[name].get_average() def get_class_average(self): """计算全班平均分""" if not self._students: return 0.0 # 对每个学生的平均分求平均 averages = [s.get_average() for s in self._students.values()] return sum(averages) / len(averages) def list_all_students(self): """列出所有学生信息""" if not self._students: print("暂无学生") return for student in self._students.values(): print(student)

解释:GradeManager内部用self._students字典管理Student对象,它调用Student的方法(如add_score,get_average),但不继承Student。这清晰划分了职责:Student管自己,GradeManager管一群Student。

5.3 第三步:运行主程序,体验“对象自己管自己”

现在,我们写一个简单的交互式主程序,让它跑起来。你会看到,所有复杂的逻辑(数据校验、平均分计算、错误提示)都被封装在类里,主程序只需调用清晰的方法名。

def main(): manager = GradeManager() # 添加学生 try: manager.add_student("张三") manager.add_student("李四") print("✅ 学生添加成功") except ValueError as e: print(f"❌ 添加失败: {e}") # 添加成绩 try: manager.add_score("张三", "数学", 85) manager.add_score("张三", "英语", 92) manager.add_score("李四", "数学", 78) manager.add_score("李四", "英语", 88) print("✅ 成绩添加成功") except ValueError as e: print(f"❌ 添加成绩失败: {e}") # 查询 print("\n--- 学生详情 ---") manager.list_all_students() print(f"\n--- 张三的平均分: {manager.get_student_average('张三'):.1f}") print(f"--- 全班平均分: {manager.get_class_average():.1f}") # 尝试错误操作 try: manager.add_score("王五", "数学", 90) # 王五不存在 except ValueError as e: print(f"⚠️ 捕获预期错误: {e}") if __name__ == "__main__": main()

运行结果:

✅ 学生添加成功 ✅ 成绩添加成功 --- 学生详情 --- 学生: 张三, 成绩: {'数学': 85, '英语': 92}, 平均分: 88.5 学生: 李四, 成绩: {'数学': 78, '英语': 88}, 平均分: 83.0 --- 张三的平均分: 88.5 --- 全班平均分: 85.8 ⚠️ 捕获预期错误: 学生 王五 不存在

5.4 常见问题排查:为什么我的GradeManager报错'GradeManager' object has no attribute '_students'?

现象:运行main()时,manager.add_student("张三")报错,说GradeManager没有_students属性。
原因:最可能的原因是GradeManager.__init__方法名写错了!比如写成了def init(self):(少了个下划线)或def __init__(self, name):(多了一个参数)。Python只会自动调用名为__init__且参数只有self(或self加其他必要参数)的方法。
解决:仔细检查__init__的拼写和参数。用print(dir(manager))可以查看对象有哪些属性,确认_students是否在其中。

现象:manager.add_score("张三", "数学", 150)没有报错,但成绩存了150。
原因:add_score方法里的校验条件写错了,比如score > 100写成了score < 100,或者漏掉了or score > 100。
解决:在add_score方法里加一行print(f"Debug: trying to set {subject}={score}"),运行看是否进入方法;然后逐行检查if条件。

现象:print(manager.get_student_average("张三"))输出0.0,但我知道他有成绩。
原因:get_student_average方法里,self._students[name]取到了学生对象,但调用get_average()时,Student.get_average()里self._scores是空字典。可能add_score没成功执行,或者_scores字典的key写错了(比如存了"shuxue"但查"数学")。
解决:在Student.get_average里加print(f"Debug scores: {self._scores}"),看实际存了什么。


6. 下一步学什么?从“会用”到“会设计”:一份南开软院风格的进阶路线图

走到这一步,你已经能读懂南开软件学院期末复习材料里的大部分内容,并亲手写出一个有封装、有校验、有错误处理的小系统。但这只是起点。真正的Python高手,不是知道多少语法,而是能在面对一个新需求时,快速判断“该用什么结构、该封装到哪里、边界在哪”。下面是我当年跟着南开导师做课程设计时,总结出的、从“会用”跃迁到“会设计”的三步走路线,每一步都配了具体可练的小任务。

6.1 第一步:把“魔法函数”从“背名字”变成“主动

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进