简介:很多Python学习者“看得懂却写不出”,症结在于对核心机制理解不透。本文按数据结构、函数机制、面向对象、迭代器与生成器、上下文管理、异常处理及标准库六大模块整理自测考点,每组均附可运行代码与输出解析。建议先预测结果再运行验证,用错题定位薄弱环节,夯实从基础到进阶的完整知识体系。
很多 Python 学习者都有类似体会:教程看得明白,代码也能读顺,可一旦合上资料独立编写,或面对一段“看似简单”的代码判断输出结果,就频频出错。这说明“看懂”和“掌握”之间存在明显断层。本文按难度递进整理了六大模块的自测点,每个知识点均配有可直接运行的代码示例。建议你先遮住注释中的输出结果,独立预测,再动手运行验证——预测与实际不符的地方,就是知识盲区所在。
Python 中变量是对象的“标签”,赋值传递的是引用而非副本。判断两个变量是否指向同一个对象要用 is,判断值是否相等用 ==。
a = [1, 2, 3]b = a # b 和 a 指向同一个列表b.append(4)print(a) # [1, 2, 3, 4],修改 b 等于修改 ax = [1, 2, 3]y = [1, 2, 3]print(x == y) # True,内容相同print(x is y) # False,两个独立对象
自测要点:日常开发中应默认使用 == 做值比较,仅在与 None、True、False 等单例比较时使用 is。如果你曾用 is 比较两个字符串或列表,说明这一关没过。
嵌套结构中,浅拷贝只复制最外层容器,内层子对象仍然共享;深拷贝才会递归复制所有层级。
import copysrc = [[1, 2], [3, 4]]shallow = src.copy() # 浅拷贝deep = copy.deepcopy(src) # 深拷贝src[0].append(99)print(shallow[0]) # [1, 2, 99],内层列表仍被共享print(deep[0]) # [1, 2],完全独立
自测要点:能说出 list.copy()、切片 [:]、dict(dict) 等操作都属于浅拷贝,并知道何时必须使用 deepcopy。
切片语法 [start
step] 含头不含尾;推导式是从“循环 + append”到声明式表达的升级。
nums = list(range(10))print(nums[2:8:2]) # [2, 4, 6]print(nums[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]matrix = [[1, 2, 3], [4, 5, 6]]flat = [x for row in matrix for x in row]print(flat) # [1, 2, 3, 4, 5, 6]
字典取值推荐用 get 避免 KeyError;集合天然适合去重与交并差运算。
scores = {'alice': 90, 'bob': 85}print(scores.get('carol', 0)) # 0,键不存在时返回默认值squares = {n: n ** 2 for n in range(4)}print(squares) # {0: 0, 1: 1, 2: 4, 3: 9}a, b = {1, 2, 3}, {2, 3, 4}print(a & b) # {2, 3} 交集print(a | b) # {1, 2, 3, 4} 并集print(a - b) # {1} 差集
默认参数在函数定义时求值一次,之后所有调用共享同一个对象,这是最经典的“隐式状态共享”陷阱。
def append_to(item, target=[]):target.append(item)return targetprint(append_to(1)) # [1]print(append_to(2)) # [1, 2],而很多人预期是 [2]# 正确写法:用 None 作哨兵值def append_to_safe(item, target=None):if target is None:target = []target.append(item)return targetprint(append_to_safe(1)) # [1]print(append_to_safe(2)) # [2]
闭包捕获的是变量本身而非创建那一刻的值,循环结束后再调用,读到的是循环变量的最终值。
funcs = [lambda: i for i in range(3)]print([f() for f in funcs]) # [2, 2, 2],而非 [0, 1, 2]# 修复:利用默认参数在定义时求值的特性固化当前值funcs = [lambda i=i: i for i in range(3)]print([f() for f in funcs]) # [0, 1, 2]
def log_call(level, *args, **kwargs):print(f"[{level}] args={args} kwargs={kwargs}")log_call("INFO", "login", "success", user_id=42)# [INFO] args=('login', 'success') kwargs={'user_id': 42}
装饰器本质是“接收函数并返回新函数”的高阶函数。@functools.wraps 用于把原函数的 name、doc 等元信息复制到包装函数上,避免函数被装饰后“改名”,破坏文档与内省。
import functoolsimport timedef timed(func):@functools.wraps(func)def wrapper(*args, **kwargs):start = time.perf_counter()result = func(*args, **kwargs)cost = time.perf_counter() - startprint(f"{func.__name__} 耗时 {cost:.4f}s")return resultreturn wrapper@timeddef slow_sum(n):return sum(range(n))print(slow_sum(10_000_000))# 先打印类似:slow_sum 耗时 0.2xxxs,随后输出求和结果
自测要点:不借助资料能写出一个计时装饰器并解释 wraps 的作用才算过关;能进一步实现“带参数的装饰器”(三层嵌套结构)属于加分项。
实例查找属性时先查自身,再沿继承链查类;对实例赋值只会“遮蔽”类属性,不会修改它。
class Dog:species = "犬科" # 类属性,所有实例共享def __init__(self, name):self.name = name # 实例属性d1, d2 = Dog("旺财"), Dog("小黑")d1.species = "猫科" # 只在 d1 上创建同名实例属性print(d2.species) # 犬科Dog.species = "哺乳类" # 通过类名修改类属性print(d2.species) # 哺乳类print(d1.species) # 猫科,实例属性优先级更高
class Vector:def __init__(self, x, y):self.x, self.y = x, ydef __add__(self, other):return Vector(self.x + other.x, self.y + other.y)def __eq__(self, other):return (self.x, self.y) == (other.x, other.y)def __repr__(self):return f"Vector({self.x}, {self.y})"print(Vector(1, 2) + Vector(3, 4)) # Vector(4, 6)print(Vector(1, 2) == Vector(1, 2)) # True
自测要点:至少掌握 init、repr、str、len、eq、getitem 等常见魔术方法的触发时机。
class Account:def __init__(self, balance=0):self._balance = balance@propertydef balance(self):return self._balance@balance.setterdef balance(self, value):if value < 0:raise ValueError("余额不能为负")self._balance = valueacc = Account(100)acc.balance = 150 # 像属性一样赋值,实则触发 setter 校验print(acc.balance) # 150
class Animal:def __init__(self, name):self.name = namedef speak(self):raise NotImplementedError("子类必须实现 speak")class Cat(Animal):def speak(self):return f"{self.name}:喵"class DogChild(Animal):def __init__(self, name, breed):super().__init__(name) # 复用父类初始化,避免重复代码self.breed = breeddef speak(self):return f"{self.name}:汪"print(Cat("咪咪").speak()) # 咪咪:喵print(DogChild("大黄", "柴犬").speak()) # 大黄:汪
for 循环会先调用 iter() 获取迭代器,再不断调用 next(),直到抛出 StopIteration。
class Countdown:def __init__(self, start):self.current = startdef __iter__(self):return selfdef __next__(self):if self.current <= 0:raise StopIterationself.current -= 1return self.current + 1for n in Countdown(3):print(n) # 依次输出 3 2 1
含 yield 的函数调用后并不立即执行,而是返回生成器对象;每次 next() 执行到 yield 处暂停并交出值,函数的局部状态被完整保留。这让生成器在处理超大数据集时内存占用极低。
def fibonacci():a, b = 0, 1while True:yield aa, b = b, a + bfib = fibonacci()print([next(fib) for _ in range(8)])# [0, 1, 1, 2, 3, 5, 8, 13]
处理大文件时同理,逐行产出而不是一次性读入:
def read_lines(path):with open(path, encoding='utf-8') as f:for line in f:yield line.strip()
total = sum(n * n for n in range(1_000_000)) # 不生成百万元素的列表squares = [n * n for n in range(10)] # 列表推导式则立即分配内存
自测要点:能说清“生成器只能消费一次”——迭代到底后再次迭代将得到空结果,需要重新创建。
with 语句在进入代码块时调用对象的 enter,退出时(无论是否发生异常)调用 exit,从而把“清理资源”从业务代码中剥离。
import timeclass Timer:def __enter__(self):self.start = time.perf_counter()return selfdef __exit__(self, exc_type, exc_val, exc_tb):self.cost = time.perf_counter() - self.startprint(f"耗时 {self.cost:.4f}s")return False # 返回 False 表示不吞掉异常with Timer():sum(range(5_000_000))# 代码块结束时打印耗时,即使块内抛异常也会执行
from contextlib import contextmanager@contextmanagerdef html_tag(tag):print(f"<{tag}>")try:yieldfinally:print(f"</{tag}>")with html_tag("div"):print("内容")# 输出:<div> / 内容 / </div>
Python 社区推崇 EAFP(先执行、出错再兜底)优于 LBYL(先检查再行动)。
config = {"host": "127.0.0.1"}# LBYL 风格:先检查port = config["port"] if "port" in config else 8080# EAFP 风格:先执行,出错再兜底(更 Pythonic)try:port = config["port"]except KeyError:port = 8080print(port) # 8080def parse_int(text):try:return int(text)except ValueError as e:print(f"无法解析 {text!r}:{e}")return Nonefinally:pass # 无论成败都执行,适合收尾清理print(parse_int("42")) # 42print(parse_int("hello")) # 打印提示并返回 None
自测要点:知道裸 except 会连 KeyboardInterrupt 一起吞掉、except 顺序要从具体到宽泛、异常链 from 的用途。
标准库是 Python“自带电池”的核心竞争力,熟练使用可以少写大量样板代码。
from collections import Counter, defaultdictwords = "the quick brown fox jumps over the lazy dog the".split()print(Counter(words).most_common(2))# [('the', 3), ('quick', 1)]groups = defaultdict(list)for name, dept in [("Alice", "dev"), ("Bob", "ops"), ("Carol", "dev")]:groups[dept].append(name)print(dict(groups))# {'dev': ['Alice', 'Carol'], 'ops': ['Bob']}
from itertools import chain, productfrom pathlib import Pathimport jsonprint(list(chain([1, 2], [3, 4]))) # [1, 2, 3, 4]print(list(product("AB", repeat=2))) # [('A','A'), ('A','B'), ('B','A'), ('B','B')]json_files = list(Path("./conf").glob("*.json")) # 面向对象方式查找文件data = {"name": "Alice", "tags": ["python"]}text = json.dumps(data, ensure_ascii=False) # 序列化为 JSON 字符串back = json.loads(text) # 反序列化
内置函数 enumerate 与 zip 让循环索引和并行遍历更简洁:
names = ["Alice", "Bob"]scores = [90, 85]for rank, (name, score) in enumerate(zip(names, scores), start=1):print(f"第{rank}名:{name},{score}分")# 第1名:Alice,90分# 第2名:Bob,85分
以上六大关约二十个考点,可按以下标准给自己定位:
三条通用建议:
Python 的易学性容易让人产生“已经掌握”的错觉,而面试与真实项目只认代码层面的硬功夫。把上面每一道示例都在本地跑一遍、改一遍、再解释一遍,你收获的将不只是知识点本身,而是把知识转化为生产力的能力。