简介《Python面试题及答案共55道》面向准备Python岗位面试的初中级开发者与计算机专业学生围绕语法基础、数据结构、函数作用域、装饰器、异常处理、字典与文件操作、正则及序列化等高频考点提供题目、参考答案与输出解析便于系统自查和查漏补缺。资源包仅含1个docx文件大小约80KB按题组集中编排适合打印或导入笔记软件后分类复习。已有1108人学习下载说明其内容对常见笔试题有一定参考价值。读者可通过55道题掌握易错点例如装饰器自下而上执行、try-except-finally中finally必执行、函数内修改全局变量需用global、os.walk遍历目录、re.match与search差异及json对datetime的定制序列化并通过代码片段理解答案推导过程提升面试应答与现场编码的准确性。1. 一份 docx 题库为什么值得逐题跑一遍面试前一晚把 docx 从头翻到尾每道题都觉得眼熟第二天手写代码却卡在a 3上——这不是记忆力问题是「看懂答案」和「能推演执行过程」之间差了一层。这份《Python 面试题及答案共 55 道》覆盖的作用域、装饰器、异常执行顺序、MRO、单例、栈队列链表手写恰好是初面到二面之间最常被追问的那批。它不适合当八股背适合当一份能上机跑的题库每道题都要自己能写出输出、能说出为什么、能补一个反例。下面按「执行顺序 → 面向对象 → 手写结构 → 踩坑」推演每段都给可复制运行的代码方便边看边改。2. 执行顺序那一组作用域、装饰器、finally 到底谁先跑这一章的四道题在题库里被标成「输出题」看起来是背答案的活儿实际上它们考的是同一件事CPython 在什么时候决定一个名字属于谁、在什么时候把代码块挂到哪个对象上。把这条线捋顺题库里剩下的输出题基本是同一个模型的变体。2.1a 3为什么直接抛 UnboundLocalError先复现原始现象a 1 def bar(): a 3 # 编译期就把 a 判定成了局部变量 return a bar() # UnboundLocalError: local variable a referenced before assignment很多人第一反应是「a不是全局的吗」。问题出在编译期而不是运行期CPython 在编译函数体时扫描所有赋值目标a 3展开是a a 3左边出现了对a的绑定于是a被记进co_varnames函数内所有对a的读取都走LOAD_FAST。执行到a 3时局部槽位还没写进去直接抛异常。def bar(): a 3 # 只要有赋值a 就是局部的 return a print(bar.__code__.co_varnames) # (a,)可以直接看到编译器怎么判的 print(bar.__code__.co_names) # ()说明没去全局找co_varnames是局部变量名元组co_names是全局/属性名元组这两个属性是排查作用域问题最直接的一手证据比在纸上推快得多。想真的改全局用global想改闭包外层变量用nonlocaldef counter(): n 0 def inc(): nonlocal n # 不加这行同样会 UnboundLocalError n 1 return n return inc c counter() print(c(), c(), c()) # 1 2 3nonlocal只向上找最近一层函数作用域找不到会直接SyntaxError不会退化成全局。这条规则也解释了为什么闭包计数器必须用可变容器或者nonlocal用普通整数赋值一定翻车。2.2 字典传进函数被改掉引用传的是引用的副本mydict {a: 1, b: 2} def func(d): d[a] 0 # 原地修改动的是同一个 dict 对象 return d func(mydict) mydict[c] 2 print(mydict) # {a: 0, b: 2, c: 2}Python 的参数传递常被描述成「传对象引用」更准确的表述是传的是指向对象的引用的副本。函数内外两个名字指向同一个 dictd[a] 0是原地写外部可见如果函数体里写d {a: 0}只是让局部名字重新绑定到新对象外部不受影响。def rebind(d): d {a: 0} # 断链外部 mydict 不变 d[x] 1 return d def mutate(d): d[a] 0 # 原地写外部可见 print(id(mydict))用id()打印一下就能看清是重新绑定还是原地修改这个习惯在排查「为什么我的配置没被改」时很省时间。同一个模型还派生出可变默认参数这个经典坑def append_item(x, bucket[]): # 默认值只在函数定义时求值一次 bucket.append(x) return bucket print(append_item(1)) # [1] print(append_item(2)) # [1, 2]不是 [2]正确写法是bucketNone然后在函数体里if bucket is None: bucket []。需要真复制的时候区分copy.copy浅拷贝嵌套对象还是共享和copy.deepcopy递归复制遇到自引用要用memo参数。2.3 两个装饰器叠加装饰期自下而上调用期自上而下def decorator_a(func): print(Get in decorator_a) # 装饰阶段执行只跑一次 def inner_a(*args, **kwargs): print(Get in inner_a) # 调用阶段执行 return func(*args, **kwargs) return inner_a def decorator_b(func): print(Get in decorator_b) def inner_b(*args, **kwargs): print(Get in inner_b) return func(*args, **kwargs) return inner_b decorator_b decorator_a def f(x): print(Get in f) return x * 2 f(1)输出是Get in decorator_a、Get in decorator_b、Get in inner_b、Get in inner_a、Get in f。把语法糖展开就一目了然f decorator_b(decorator_a(f))离函数最近的decorator_a先包decorator_b在外面再包一层。所以装饰期的打印顺序是自下而上。调用f(1)时最外层包装inner_b先被调用它内部再调inner_a最后才到原函数打印顺序自上而下。这两句话背下来没意义展开成赋值表达式一次就记住了。工程上还有一个必加的细节import functools def decorator_a(func): functools.wraps(func) # 把 __name__ / __doc__ / __module__ 搬过来 def inner_a(*args, **kwargs): return func(*args, **kwargs) return inner_a不加wrapsf.__name__会变成inner_b基于函数名做的路由注册、日志标记、inspect.signature取参数全部失真。functools.wraps还会顺手写一个__wrapped__属性指向原函数调试时用inspect.unwrap()能一层层剥回去。2.4 try/except/finally 里 return 到底谁说话def test(): try: raise ValueError(something wrong) except ValueError as e: print(error occured) return 1 finally: print(ok) print(test())输出error occured、ok、1。finally在return真正交还控制权之前执行所以打印在返回值落地之前。但如果在finally里也写return它会覆盖掉try/except里的返回值和异常def swallow(): try: raise ValueError(boom) finally: return 0 # 异常被静默吞掉调用方看到的是 0 print(swallow()) # 0没有 traceback这是血泪经验里最贵的一条线上排查一个「接口返回 0 但日志没有异常」的问题最后定位到就是某个清理函数在finally里写了return。finally里只做释放资源绝不放return或break。顺带把with的执行模型对上class W: def __enter__(self): print(进入with语句) return self def __exit__(self, exc_type, exc, tb): print(退出with语句) return False # 返回 True 会吞掉异常和 finally 里 return 一个性质 with W() as w: print(之前) print(w) print(之后)__exit__的三个参数在无异常时全是None返回值决定异常是否继续往外抛。用contextlib.contextmanager写生成器版本时yield之后的清理代码等价于__exit__try/finally包住yield才安全。3. 面向对象那一组MRO、super、单例的三条主线题库里 OOP 部分占了将近三分之一看着散其实是三条主线多继承下方法从哪来C3 与 MRO、子类怎么正确复写父类逻辑super、怎么控制实例的创建次数单例与元类。这三条串起来鸭子类型、isinstance、魔术方法那些零散题就能挂上去。3.1 C3 与__mro__多继承的方法到底从哪来class A: def who(self): return A class B(A): def who(self): return B class C(A): def who(self): return C class D(B, C): pass print([c.__name__ for c in D.__mro__]) # [D, B, C, A, object] print(D().who()) # B__mro__就是方法解析顺序属性查找沿着这个元组从左到右走第一个命中的胜出。C3 算法要同时满足三条约束子类一定排在父类之前多个基类之间保持声明顺序整条序列保持单调某个类的相对顺序不在不同分支里翻转。约束满足不了就会直接拒绝建类class A: pass class B(A): pass class C(A): pass class D(B, C): pass class E(C, B): pass class F(D, E): pass # TypeError: Cannot create a consistent method resolution # order (MRO) for bases B, CD的 MRO 要求 B 在 C 前E的 MRO 要求 C 在 B 前F同时继承两者约束冲突。实话说这个报错在业务代码里极少遇到遇到基本说明继承图设计得过于花哨扁平化比调 MRO 划算。面试被问到能说出三条约束、能现场手推一个菱形继承的序列就够用了。3.2 super 不是「调父类」是「调 MRO 上的下一个」class A: def __init__(self): print(A) class B(A): def __init__(self): print(B) super().__init__() class C(A): def __init__(self): print(C) super().__init__() class D(B, C): def __init__(self): print(D) super().__init__() D() # D B C A如果super真是「调父类」B.__init__里的super()应该调到A那C就不会被打印。实际输出里C出现了因为D的 MRO 是D → B → C → AB里的super()在这个实例的 MRO 上找的是C。d D() print(type(d).__mro__)super()的零参数写法依赖编译期注入的__class__单元格和调用栈第一帧的self所以在类体外、在嵌套函数里直接用会报RuntimeError: super(): no arguments。真要显式写super(B, self).__init__()和super().__init__()在单继承下等价在多继承下前者会跳过 MRO 中B之前的项属于自己给自己挖坑。还有一个容易忽略的点所有基类必须配合super()形成一条完整的链只要有某一层硬写A.__init__(self)链就断了后面的兄弟类不再被初始化。写 mixin 的时候尤其要守住这条。3.3 单例的两种写法__new__与元类锁放在哪import threading class Singleton: _instance None _instance_lock threading.Lock() def __new__(cls, *args, **kwargs): if cls._instance is None: # 第一次检查避免每次都抢锁 with cls._instance_lock: if cls._instance is None: # 第二次检查抢到锁后再确认 cls._instance super().__new__(cls) return cls._instance def __init__(self): pass a, b Singleton(), Singleton() print(a is b) # True双检锁的关键在于两层检查各自的作用外层挡住绝大多数并发请求内层挡住「两个线程同时通过外层」的那一小段窗口。少了内层就会创建两个实例少了外层每次构造都要抢锁性能直接掉一个量级。用hasattr(cls, _instance)判断也可以但hasattr会把None也当成「已有」如果某个子类提前把_instance置成None判断就失效。直接判is None更稳。元类版本适合「一个元类管多个单例类」的场景class SingletonMeta(type): _instances {} _lock threading.Lock() def __call__(cls, *args, **kwargs): if cls not in cls._instances: with cls._lock: if cls not in cls._instances: cls._instances[cls] super().__call__(*args, **kwargs) return cls._instances[cls] class Config(metaclassSingletonMeta): pass class Cache(metaclassSingletonMeta): pass print(Config() is Config(), Cache() is Cache()) # True True__call__挂在元类上管的是「类被调用」这个动作所以Config()走的是SingletonMeta.__call__。字典按cls分键一个元类可以管住任意多个类。__new__版本则要么每个类写一遍要么抽一个基类往下继承灵活性差一截。两种写法都要注意__init__每次构造都会重新执行一遍。单例只保证实例唯一不保证初始化一次需要「只初始化一次」就得自己加标志位或者把初始化逻辑挪到__new__里。3.4 上下文管理器与__call__with 与 callable 的判定边界from contextlib import contextmanager contextmanager def opened(path, modew): f open(path, mode) try: yield f finally: f.close() # 不管 with 体内抛不抛异常都会关 with opened(/tmp/demo.txt) as f: f.write(hello)生成器版本把yield之前当__enter__、之后当__exit__try/finally必须包住yield否则体内抛异常时文件句柄泄漏。__exit__返回True会吞异常返回None/False继续往外抛这个行为和 2.4 节里的finally是同一套逻辑。callable的判定标准只有一个类型对象的tp_call槽位是否非空等价于类里有没有定义__call__。class Adder: def __call__(self, x, y): return x y add Adder() print(callable(add)) # True print(add(1, 2)) # 3 print(callable(Adder)) # True类天然可调用 print(callable(len)) # True内置函数 print(callable(abc)) # False类本身可调用是因为type上有tp_call实例可调用则取决于类有没有__call__。callable()和hasattr(obj, __call__)在绝大多数情况下等价但对某些用 C 实现的类型hasattr可能因为属性描述符行为而返回不同结果判断「能不能调」优先用callable。3.5isinstance与type为什么子类判断会翻车class A: pass class B(A): pass b B() print(type(b) A) # False print(type(b) is B) # True print(isinstance(b, A)) # True print(isinstance(b, (A, int))) # True第二个参数可以是元组type(x)返回精确类型比较的是类型对象本身不走 MROisinstance会沿__mro__往上找。判断「是不是某类家族」用isinstance判断「是不是恰好这个类型」才用type(x) is T。顺带一个反直觉的例子isinstance(True, int)返回True因为bool继承自int。写参数校验时如果只判isinstance(x, int)True会通过需要额外排除bool。另一个边界是isinstance对typing泛型别名的行为isinstance([], list[int])在低版本直接抛TypeError跨版本代码别依赖这个。4. 手写结构那一组栈、队列、链表和九层循环题库后半段是纯手写题链表栈、双队列栈、单链表逆置、手写队列、九层循环的竖式谜题。这些题不考记忆考的是能不能把复杂度算清楚、把边界写全。面试官追问的通常不是「能不能写出来」而是「你这个实现 pop 是 O(1) 还是 O(n)」。4.1 竖式谜题九层循环怎么剪枝到能秒出原题是九个字母各不相同的竖式加法原始写法九层for加逐层判重组合量在十的八次方量级跑起来肉眼可见地卡。先看约束from itertools import permutations # 首字母 A/C/E/G/P 不能为 0permutations(range(10), 9) 已经保证互不相同 cnt 0 for A, B, C, D, E, F, G, H, P in permutations(range(10), 9): if 0 in (A, C, E, G, P): continue ab A * 10 B cd C * 10 D ef E * 10 F gh G * 10 H ppp P * 100 P * 10 P if ab - cd ef and ef gh ppp: cnt 1 print(A, B, C, D, E, F, G, H, P) print(solutions:, cnt)permutations(range(10), 9)是 3628800 组比九层嵌套加if in判重的写法小两个数量级。逻辑说明permutations保证九个字母互不相同省掉所有判重分支首字母判 0 是因为两位数不能以 0 开头三位数PPP更不能。再往下压一层用算术关系先把 P 定死# 两个两位数相加最大 98 98 196唯一的 repdigit 三位数是 111 # 所以 P 1EF GH 111且 EF 12、GH 12EF 最大 98 for E in range(1, 10): for F in range(10): ef E * 10 F gh 111 - ef if not (12 gh 98): continue G, H divmod(gh, 10) if len({E, F, G, H, 1}) 5 or 0 in (G,): continue # AB - CD EFAB 98CD 12所以 EF 86 if ef 86: continue for A in range(1, 10): for B in range(10): ab A * 10 B cd ab - ef if not (12 cd 98): continue C, D divmod(cd, 10) if len({A, B, C, D, E, F, G, H, 1}) 9 and 0 not in (A, C): print(A, B, C, D, E, F, G, H, 1)剪枝的依据是取值范围而不是玄学PPP必须落在 111 到 196 之间且三位相同只有 111 成立EF和GH都是两位数所以EF落在 12 到 98。先枚举量最小的一层外层循环次数直接掉到百级。这种「先用等式关系把变量消掉再枚举」的路子在笔试里比优化循环体写法有用得多。4.2 链表栈和双队列栈复杂度账要算清楚class Node: __slots__ (val, next) # 省掉 __dict__节点内存小一半 def __init__(self, val): self.val val self.next None class LinkedStack: def __init__(self): self.top None self._size 0 def push(self, val): node Node(val) node.next self.top # 头插 self.top node self._size 1 def pop(self): if self.top is None: raise IndexError(pop from empty stack) node self.top self.top node.next self._size - 1 return node.val def __len__(self): return self._sizepush和pop都是 O(1)peek也是 O(1)。代价是每个元素多一个指针字段和一次对象分配内存密度远低于列表遍历时缓存命中率也差。题库里那个pop返回None表示空栈的写法要改空栈pop抛IndexError才是和list一致的语义静默返回None会让调用方分不清「空栈」和「栈里存了个 None」。class TwoQueueStack: def __init__(self): self.q_in [] self.q_out [] def push(self, val): self.q_in.append(val) # O(1) def pop(self): if not self.q_in and not self.q_out: raise IndexError(pop from empty stack) if not self.q_out: while self.q_in: # 把 q_in 全部倒进 q_out顺序反转 self.q_out.append(self.q_in.pop()) return self.q_out.pop() # 摊还 O(1) def __len__(self): return len(self.q_in) len(self.q_out)这个写法每次元素最多被搬两次摊还 O(1)如果按题库里「每次 pop 都把 n-1 个元素倒来倒去」的写法单次 pop 是 O(n)。两种实现对比如下实现pushpop额外内存适用场景链表栈O(1)O(1)每元素一个指针元素大小不均、频繁增删双队列栈懒倒O(1)摊还 O(1)两个列表纯 Python、要缓存友好list直接 append/popO(1) 摊还O(1)无绝大多数业务场景实话说纯 Python 里手写链表栈没有性能优势list的append/pop()在尾部操作就是摊还 O(1)还省掉节点对象。手写题的意义在于能说清「为什么这里不用链表」而不是真的拿去替换list。4.3 单链表逆置三指针模板和它的边界class ListNode: def __init__(self, val0, nextNone): self.val val self.next next def reverse_list(head): prev None cur head while cur: nxt cur.next # 先存后继不然改完 next 就找不到路了 cur.next prev # 当前节点指向前驱完成反转 prev cur # 前驱后移 cur nxt # 当前后移 return prev # 循环结束时 cur 是 Noneprev 是新头 def build(vals): dummy ListNode() tail dummy for v in vals: tail.next ListNode(v) tail tail.next return dummy.next def to_list(head): out [] while head: out.append(head.val) head head.next return out print(to_list(reverse_list(build([1, 2, 3, 4])))) # [4, 3, 2, 1] print(to_list(reverse_list(build([])))) # [] print(to_list(reverse_list(build([1])))) # [1]三个指针的语义要能一句话说清prev是已反转部分的头cur是待处理部分的头nxt是临时保存的下一站。循环不变量是「prev之前的全部已反转cur之后的全未处理」。空链表和单节点两种情况在while cur里自然覆盖不需要额外判空很多人习惯性加if not head or not head.next加了也不错但要知道不是必需的。递归版本更短但 Python 默认递归深度 1000 左右链表超过 900 个节点就有RecursionError风险线上代码不要用。用sys.setrecursionlimit调深度是治标栈帧爆掉照样段错误。4.4 目录遍历、正则、序列化边界比主逻辑更容易挂import os def walk_paths(root, skip(.git, __pycache__, node_modules)): for dirpath, dirnames, filenames in os.walk(root, topdownTrue, followlinksFalse): dirnames[:] [d for d in dirnames if d not in skip] # 原地改剪枝生效 for name in dirnames: yield os.path.join(dirpath, name) for name in filenames: yield os.path.join(dirpath, name)os.walk返回三元组(dirpath, dirnames, filenames)dirnames是列表原地切片赋值才能让剪枝生效重新绑定成新列表dirnames [...]不会影响遍历。topdownTrue是默认值自顶向下followlinksFalse也是默认值不跟随符号链接在存在链接环的目录树上这是保命的跟了会无限递归。os.listdir递归版要自己判os.path.isdir注意这个函数默认跟随符号链接和os.walk的默认行为不一致混用会得到两套结果。删除相关的三个函数边界差别很大import os, shutil os.remove(a.txt) # 只能删文件删目录抛 IsADirectoryError os.rmdir(empty_dir) # 只能删空目录非空抛 OSError shutil.rmtree(any_dir) # 递归删非空也删不进回收站无法撤销shutil.rmtree是最需要加确认的那个一次误调用就是不可恢复。生产脚本里我一般先print目标路径再等一个input()宁可多一步。正则这块match和search的差别只有一句话match从 0 位置开始匹配search扫描整个字符串。用邮箱校验时锚定写清楚更保险import re EMAIL re.compile(r^[A-Za-z0-9._%-][A-Za-z0-9.-]\.[A-Za-z]{2,}$) print(bool(EMAIL.match(1403179190example.com))) # True print(bool(EMAIL.match(x 1403179190example.com))) # False开头有空格 print(bool(EMAIL.search(x 1403179190example.com)))# Truesearch 会跳过前缀贪婪和非贪婪的区别落在量词上.*尽量多吃.*?吃到能匹配就停。解析 HTML 片段时用.*会把整个文档吞进去这是最常见的翻车点正确做法是.*?或者干脆别用正则解析 HTML。json 序列化的三个高频参数import json from datetime import datetime class ComplexEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.strftime(%Y-%m-%d %H:%M:%S) return super().default(obj) # 交给父类抛 TypeError d {name: alex, created: datetime(2024, 5, 18, 19, 52, 5)} print(json.dumps(d, clsComplexEncoder)) # 中文被转成 \uXXXX print(json.dumps(d, clsComplexEncoder, ensure_asciiFalse)) # 保留中文 print(json.dumps(d, clsComplexEncoder, ensure_asciiFalse, indent2, sort_keysTrue))cls指定自定义编码器default只对标准编码器处理不了的对象调用返回可序列化对象ensure_asciiFalse决定非 ASCII 字符是转义还是原样输出写日志和对外接口时一般要关掉sort_keys和indent主要影响可读性和 diff 稳定性线上报文别加indent白白多传输。5. 避坑与排查五条“答案对但理解错”的记录这一章的五条都是实际跑代码时撞出来的题库答案本身没错错在对答案的解释。每条按现象、原因、解决顺序写方便照着复现。5.1a 3在 REPL 里不报错写进函数就炸现象在交互式环境里敲a 1; a 3一切正常把同样两行放进函数体就抛UnboundLocalError。原因模块级和函数级是两套作用域规则。模块级的名字查找走LOAD_NAME找不到就去内置命名空间兜底赋值直接写进模块字典函数体内只要出现绑定就进co_varnames读取走LOAD_FAST编译期就定死了。解决先print(func.__code__.co_varnames)确认编译器把哪些名字判成了局部再决定加global还是nonlocal。临时验证用dis.dis(func)看字节码LOAD_FAST和LOAD_GLOBAL一眼分清。5.2 不加functools.wraps路由注册和日志全乱套现象给视图函数套了两个装饰器之后框架报「重复注册同名路由」日志里的函数名全是inner_b。原因装饰器返回的是内层包装函数__name__、__doc__、__module__都变成了内层的。基于func.__name__建索引的框架直接把所有被装饰的函数认成同一个。解决所有装饰器无条件加functools.wraps(func)。已经上线的代码用inspect.unwrap(f)拿到原函数补救但根治还是补上wraps。5.3finally里写了return异常被静默吞掉现象某个清理函数明明上游抛了异常调用方却拿到正常返回值日志里一条错误记录都没有。原因finally块里的return会覆盖try/except中的返回值和正在传播的异常异常对象直接被丢弃。解决finally里只做资源释放不放return、break、continue。用contextlib.contextmanager重写清理逻辑时yield必须包在try/finally里异常处理交给__exit__的返回值控制。补一条静态检查规则把finally里的return直接标红。5.4 目录遍历跑不完CPU 一直满现象对某个业务目录做os.walk脚本跑十几分钟不出结果内存还一直涨。原因目录树里存在指向祖先目录的符号链接followlinksTrue时会绕着环无限递归路径字符串越来越长。解决确认os.walk(..., followlinksFalse)这是默认值但很多代码显式传了True。自己写的os.listdir递归版本要额外判os.path.islink因为os.path.isdir默认跟随链接。再用os.path.realpath维护一个已访问 inode 集合兜底import os def walk_safe(root): seen set() for dirpath, dirnames, filenames in os.walk(root, followlinksFalse): real os.path.realpath(dirpath) if real in seen: dirnames[:] [] continue seen.add(real) yield from (os.path.join(dirpath, n) for n in filenames)5.5json.dumps之后中文全变成转义序列日志没法看现象日志里写进去的报文全是\u4e2d\u6587这种排查问题时得先手动解码一遍。原因json.dumps的ensure_ascii默认是True所有非 ASCII 字符都会被转义成\uXXXX。解决写日志、写文件、给前端返回时统一ensure_asciiFalse同时确保输出流的编码是 UTF-8。序列化datetime报TypeError时不要用defaultstr糊过去那会把datetime变成带空格的字符串下游解析格式对不上老老实实写一个JSONEncoder子类default里按类型分支返回固定格式。6. 进阶把 55 道题做成可回归的自测脚本零散地跑代码验证过两周就忘。我的做法是把题库按考点拆成测试用例用unittest的subTest做参数化一条命令跑完改坏了立刻知道。这样做的好处是把「我记得答案是 B」变成「代码跑出来必须是 B」面试前过一遍比翻十遍 docx 都管用。import unittest import copy class TestScope(unittest.TestCase): def test_unbound_local(self): def bar(): a 3 # noqa: F821 with self.assertRaises(UnboundLocalError): bar() def test_global_fix(self): global g g 1 def bar(): global g g 3 bar() self.assertEqual(g, 4) class TestMutability(unittest.TestCase): def test_dict_passthrough(self): d {a: 1, b: 2} def func(x): x[a] 0 return x func(d) d[c] 2 self.assertEqual(d, {a: 0, b: 2, c: 2}) def test_shallow_vs_deep(self): src {inner: [1, 2]} shallow copy.copy(src) deep copy.deepcopy(src) src[inner].append(3) self.assertEqual(shallow[inner], [1, 2, 3]) # 浅拷贝共享内层 self.assertEqual(deep[inner], [1, 2]) # 深拷贝隔离 class TestDecoratorOrder(unittest.TestCase): def test_order(self): trace [] def deco(name): def wrapper(func): trace.append(fdeco:{name}) def inner(*a, **kw): trace.append(fcall:{name}) return func(*a, **kw) return inner return wrapper deco(b) deco(a) def f(x): return x * 2 self.assertEqual(f(1), 2) self.assertEqual(trace, [deco:a, deco:b, call:b, call:a]) class TestTryFinally(unittest.TestCase): def test_finally_runs_before_return(self): trace [] def test(): try: raise ValueError(boom) except ValueError: trace.append(except) return 1 finally: trace.append(finally) self.assertEqual(test(), 1) self.assertEqual(trace, [except, finally]) def test_finally_return_swallows(self): def swallow(): try: raise ValueError(boom) finally: return 0 self.assertEqual(swallow(), 0) # 异常被吞这条断言本身就是警钟 if __name__ __main__: unittest.main(verbosity2)这段脚本的关键不在断言写得多漂亮在三个设计选择。第一trace列表比print可靠print的输出顺序要人眼比对断言是机器比对跑 CI 也不会漏。第二test_finally_return_swallows这条用例故意断言「异常被吞」把错误行为固化成文档新人看到这条就知道为什么代码规范里禁finally里return。第三subTest适合批量参数化比如把十道正则题写成for pattern, text, expected in cases:加with self.subTest(patternpattern):单条失败不影响其余用例继续跑。如果要再进一步可以把 OOP 那组也纳进来用D.__mro__的元组做断言校验 C3 顺序用a is b校验单例用assertRaises(TypeError)校验 MRO 冲突的类定义。数据结构那组则适合用unittest加随机化生成 1000 个随机操作序列同一序列分别喂给list和手写栈逐步比对pop结果这类差分测试比手写十组用例更能抓边界。从那以后我每次拿到这类题库文档第一步都不是从头读到尾而是先建一个test_quiz.py把每道题的「预期输出」写成断言跑通再回头补解释。解释写不出来说明只是背下了答案断言写不出来说明连预期行为都没搞清楚。这个习惯帮我筛掉了不少「看着会、上手废」的知识点。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
HBuilderX云打包全流程与证书管理实战指南 1. HBuilderX云打包全流程解析 作为一名长期使用HBuilderX进行跨平台开发的前端工程师,我深知云打包功能对于uni-app开发者而言有多重要。相比本地打包,云打包无需配置复杂的原生环境,特别适合快速迭代的移动端项目。下面我将结合自己多次实战… · 2026/11/4 17:15:12
Ricon组态系统:工业4.0数据可视化与实时响应解决方案 1. Ricon组态系统与工业4.0的可视化变革 在工业自动化领域干了十几年,我亲眼见证了从传统PLC控制到现代智能工厂的演进过程。Ricon组态系统的出现,恰好解决了工业4.0时代最棘手的问题——如何让海量工业数据真正"活"起来。这套系统不是简单的H… · 2026/11/4 17:15:12
低成本机器人导航方案:从SLAM到动态避障实战 1. 项目概述:当机器人学会自己认路十年前我第一次接触机器人导航时,需要三个工程师配合:一个负责处理传感器数据,一个调试运动控制,还有一个专门盯着防止撞墙。现在用一套开源工具链,周末在家就能让扫地机器… · 2026/11/4 17:14:11
Claude AI编程助手实战:提升开发效率的高级技巧 1. 项目概述"claude code实战二"这个标题让我眼前一亮。作为一名长期关注AI编程辅助工具的技术博主,我深知Claude作为新兴的AI编程助手正在改变开发者们的工作方式。这个标题暗示着我们将深入探讨Claude在实际编码中的应用案例,而且是系列文章… · 2026/11/4 18:11:29
B+树索引原理与数据库性能优化实践 1. 索引数据结构选型的核心逻辑数据库索引本质上是一种加速数据检索的辅助数据结构,其设计目标是在存储空间、查询效率、更新成本三者间取得平衡。当我们需要从千万级数据表中快速定位某条记录时,全表扫描的O(n)时间复杂度显然不可接受,而索引… · 2026/11/4 18:11:29
Python搭建物联网天气数据中间层服务实践 1. 项目背景与核心需求最近在开发一个物联网项目时,需要让多个终端设备实时获取当地天气数据。传统方案是每个设备单独调用天气API,但这会导致API调用次数激增,不仅产生额外费用,还可能触发限流机制。于是想到用Python搭建一个中间… · 2026/11/4 18:11:29
Agent开发实战:Skill与Tool的本质差异及技能库自演进与动态加载机制 1. 从一次翻车说起:为什么 Tool 够用,Skill 却非有不可去年冬天我接了个私活,帮一家做跨境电商的朋友搭一套自动处理售后邮件的 Agent。需求听起来不复杂:读邮件、判断是退货还是换货、查订单状态、生成回复草稿、必要时转人工。我… · 2026/11/4 18:11:29
CSS属性继承机制解析与应用实践 1. CSS属性继承机制解析CSS属性继承是前端开发中一个看似简单却容易踩坑的知识点。当我们在父元素上设置某些样式时,这些样式会自动应用到子元素上,这就是CSS继承的核心机制。理解哪些属性可继承、哪些不可继承,对于编写高效、可维护的CSS代码… · 2026/11/4 18:11:29
Jev推理模型接入指南:从概念到Codex配置与参数调优 最近总有朋友问我,Jev到底是个什么东西。每次在群里看到那张“Jev模型官网”的截图,大家的第一反应都是:这又是什么新玩具?怎么所有人都在刷?我花了两天时间,把这东西从概念到接入、从API调用到Codex里的配… · 2026/11/4 18:10:28
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38