第3章 Python语言基础高频面试题
本章聚焦变量作用域、函数机制、面向对象、装饰器、生成器与GIL六大核心。每一题都附有回答框架与避坑指南,帮助你在面试中快速组织语言、展现深度。
本章核心知识速览
| 主题 | 高频考点 | 典型题型 |
|---|---|---|
| 变量 | LEGB规则、global与nonlocal | 代码输出分析、闭包变量修改 |
| 函数 | 默认参数陷阱、可变参数、闭包 | 装饰器实现日志/权限 |
| 面向对象 | MRO、super、@property、__slots__ | 设计模式实现、多重继承Diamond问题 |
| 装饰器 | 函数装饰器、类装饰器、functools.wraps | 实现缓存装饰器、权限校验 |
| 生成器 | yield/next、生成器表达式、协程 | 大数据流式处理、无限序列 |
| GIL | 全局解释器锁、多线程 vs 多进程 | CPU密集场景如何提升性能 |
面试问题 1
Q:请解释 Python 的变量作用域规则(LEGB),并说明 global 和 nonlocal 的区别。
考察点:作用域链理解、闭包机制、面试者对 Python 变量查找顺序的掌握程度。
回答框架:
- 先说出 LEGB 全称并解释每层含义(Local → Enclosing → Global → Built-in)。
- 指出
global用于在函数内修改全局变量,nonlocal用于修改嵌套作用域(闭包外层)的变量。 - 举一个简单的嵌套函数例子说明
nonlocal的使用场景。
简短示例回答: LEGB 指 Python 查找变量时依次搜索 Local(函数内部)、Enclosing(外层函数)、Global(模块级)、Built-in(内置作用域)。`global` 声明变量来自全局,允许修改;`nonlocal` 用于闭包中修改外层函数的局部变量,但不能影响全局。
可能追问:
- 如果在嵌套函数中既想修改外层变量又想保留全局变量,应该怎么做?
- 解释一下
locals()和globals()的区别。
避坑提醒: 避免在函数内部直接对全局变量赋值而不使用 global,会创建新的局部变量而非修改全局。测试时注意嵌套作用域中 nonlocal 只能向上查找一层,不能跳跃。
面试问题 2
Q:Python 函数默认参数有哪些陷阱?请结合闭包解释 lambda 循环中变量捕获问题。
考察点:默认参数求值时机、可变对象作为默认参数的风险、闭包延迟绑定。
回答框架:
- 默认参数在函数定义时求值一次,如果参数是可变对象(如列表、字典),多次调用会共享同一对象。
- 闭包捕获的变量是引用而非值,
lambda i=i: i可以将当前值绑定到默认参数解决。 - 给出修复后的代码示例加深印象。
简短示例回答: 默认参数陷阱:`def f(a=[])` 每次调用都修改同一个列表。闭包陷阱:`lambdas = [lambda: i for i in range(3)]` 中所有 lambda 返回 2。解决:使用默认参数 `lambda i=i: i` 或 `functools.partial`。
可能追问:
- 如何实现一个每次调用都生成新默认参数的装饰器?
functools.lru_cache默认参数如何处理?
避坑提醒: 写代码时避免使用可变对象作为默认参数,改为 None 并在函数体内部初始化。闭包中如果用到循环变量,务必使用默认参数或 functools.partial 进行绑定。
面试问题 3
Q:谈谈装饰器和生成器的工作原理,以及 GIL 对多线程并发的影响。
考察点:Python 高级特性理解、生成器内存优化、GIL 局限性。
回答框架:
- 装饰器本质是函数,接收函数返回扩展后的函数;生成器使用
yield暂停/恢复状态,按需生成值。 - GIL 让一个 Python 进程同一时刻只有一个线程执行字节码,CPU 密集任务无法利用多核。
- 应对方案:I/O 密集用多线程,CPU 密集用多进程或 C 扩展。
简短示例回答: 装饰器通过闭包实现函数增强,生成器通过 `yield` 实现惰性求值,节省内存。GIL 使得多线程无法真正并行计算,适合 I/O 密集型;CPU 密集型应使用 `multiprocessing` 或 `concurrent.futures.ProcessPoolExecutor`。
可能追问:
- 如何用装饰器实现一个带参数的缓存(如
@cache(ttl=60))? - GIL 在 Python 3.12 中是否被移除?当前替代方案有哪些?
避坑提醒: 装饰器内层函数要使用 functools.wraps 保留元信息。生成器被 close() 后再次调用会抛出 StopIteration。不要认为 Python 多线程一定能加速,需先分析任务类型。
项目案例模板:实时用户行为采集与分析平台
| 维度 | 内容 |
|---|---|
| 项目名称 | 用户行为实时采集与清洗系统 |
| 业务背景 | 支撑公司 200+ 产品线的行为数据接入,进行实时清洗、聚合,为运营提供分钟级报表。 |
| 规模指标 | 日均处理 1.2 亿条事件,峰值 5k QPS;数据延迟 < 30s;存储 7 天原始数据(约 10TB)。 |
| 技术栈 | Python 3.9 + Flask(API 接收) + Celery + Redis(队列/缓存) + PostgreSQL(元数据) + Kafka(数据管道) + S3(冷存储)。 |
| 核心难点 | 高并发下数据幂等性、重复消费避免;GIL 下 I/O 密集任务线程池调优;生成器流式读取大文件避免 OOM。 |
| 面试高频追问 |
|
| 可迁移回答话术 | “我们采用生产者-消费者模式,用 Celery 任务队列解耦。遇到 GIL 瓶颈时,通过 multiprocessing 启动多个 worker 子进程,每个子进程内再使用协程处理 I/O。数据清洗使用生成器链,避免加载全部数据到内存,同时利用 yield from 实现 pipeline。最终通过 Redis 置位和数据库唯一约束保证幂等。” |
💡 项目回答加分点: 主动提及用生成器解决内存问题、用多进程绕过 GIL、用 Redis 实现分布式锁去重,能体现你对 Python 底层机制的理解和工程落地能力。
⚠ 避坑提醒: 面试官如果追问 “GIL 下 Python 多线程到底有没有用” 时,不要只回答 “没用”。要区分 I/O 密集和 CPU 密集,并举例说明 threading 结合 asyncio 的混合方案。