面试题精讲:Python 语言特性

Python 语言特性常以「理解底层逻辑」而非「死记硬背」区分候选人,本文梳理了高频考点,分类整理、删除冗余、突出核心,控制在3000字内。


一、基础变量与作用域

1. 参数传递机制:既非值也非引用,是「传对象引用」

核心结论与道满笔记(保留,太形象了):

  • 不可变对象(int/str/tuple):函数内修改会生成新对象,仅改变「临时标签」指向,原变量不受影响
  • 可变对象(list/dict/set):直接操作原内存,所有指向该地址的变量都感知

道满笔记: 类型属于对象,变量只是标签。函数复制标签贴过去:贴箱子(可变)掏东西全看见;贴石头(不可变)换个石头贴,原石头标签还在。

def modify(x, y):
    x = 2          # 临时标签x指向新对象
    y.append(1)    # 操作原可变对象

a = 1
b = []
modify(a, b)
print(a, b)  # 输出: 1 [1]

2. is vs ==:身份 vs 值

  • ==:调用__eq__比较值
  • is:对比id(),判断是否为同一块内存地址

面试陷阱: Python 会缓存 -5~256 的小整数和部分短字符串,超出范围的对象需注意。

3. LEGB 作用域查找规则

从内到外依次查,找不到报错:

  1. L (Local):当前函数/方法内部
  2. E (Enclosing):嵌套的外层函数(闭包核心)
  3. G (Global):当前模块全局
  4. B (Built-in):Python 内建(如len/str

二、核心函数与类

4. 实例/类/静态方法

理解绑定关系是关键:

方法类型装饰器首参数绑定对象核心场景
实例方法self实例访问/修改实例状态(最常用)
类方法@classmethodcls工厂模式(通过cls调用构造函数)
静态方法@staticmethod类内工具函数(与类/实例状态无关)

5. 类变量 vs 实例变量

这是最容易掉的属性查找坑:

  • 类变量:所有实例共享(除非显式覆盖),用于计数器、默认配置
  • 实例变量__init__中初始化,每个实例独有

避坑原则: 除非明确共享数据,否则永远在__init__里写实例变量

# 场景1:不可变覆盖
class Person: name = "default"
p1 = Person()
p1.name = "Jack"  # 创建实例变量覆盖查找
print(Person.name, p1.name)  # default Jack

# 场景2:可变污染
class Container: items = []
c1, c2 = Container(), Container()
c1.items.append(1)  # 直接操作类的列表内存
print(c2.items)  # [1]

6. __new__ vs __init__:诞生 vs 装修

  • __new__类方法,负责创建实例(返回对象)
  • __init__实例方法,负责初始化实例(不返回值)

先有__new__的毛坯房,才有__init__的装修队。

7. Python 3 中的super()

核心意义:维护 MRO 链条,解决多继承(菱形)问题

  • Python 3 简化为super().__init__(),避免硬编码父类
  • 根据 C3 算法生成的 MRO 列表(可用ClassName.mro()查看)调用下一个类的方法,确保每个父类只初始化一次

三、内存管理与性能优化

8. 深/浅拷贝 vs 赋值

处理嵌套对象时的内存管理核心:

  • 赋值(b=a:仅增加标签,完全等价
  • 浅拷贝(copy.copy/切片a[:]:创建新容器,但子对象引用原地址
  • 深拷贝(copy.deepcopy:递归拷贝所有嵌套对象,新老对象完全独立

9. 垃圾回收(GC)机制

引用计数为主,标记清除+分代回收为辅

  1. 引用计数(主要):每个对象有ob_refcnt,归零即释放;无法处理循环引用
  2. 标记清除:解决循环引用,找「不可达根对象」的对象
  3. 分代回收(优化):基于「活越久越难死」假设,分为0/1/2代,高代检查频率更低

10. 迭代器与生成器:内存友好的懒加载

  • 列表:一次性加载所有数据到内存(易爆)
  • 生成器:特殊的迭代器,边循环边计算,仅记住当前位置,调用next()才生成下一个
    • 生成器函数:用yield代替return
    • 生成器表达式:(x for x in range(1000000))(比列表推导式[]省内存)

11. Python 3 的range()就是迭代器

(合并原27、30)Python 2中range()返回列表、xrange()是迭代器;Python 3废弃xrange(),现有range()固定内存的懒加载迭代器,循环百万级数据无压力。


四、Python 3 专属/演进细节

12. 字符串格式化首选 f-string

(合并原8)按速度和可读性排序:

  1. f-string (Py3.6+):最快、最直观,支持{}内写表达式和格式化控制
  2. .format():功能强大,支持位置/关键字参数
  3. %占位符:古老稳健,但易踩元组参数坑
name, score = "道满", 98.5
print(f"{name} 的得分是 {score:.2f}")

13. 经典 Python 2 vs 3 差异速查

(合并原27精简版)虽然 Python 2 已淘汰,但面试官常考历史:

  • print:从语句→print()函数
  • 编码:默认全 UTF-8,字符串全 Unicode
  • 整除:3/21.5(浮点数)

五、高阶特性与设计模式入门

14. 闭包:让函数有「记忆」

定义:内嵌函数引用外层函数变量,且外层返回该内嵌函数,形成闭包

  • 关键:即使外层执行完,被引用的变量仍存活在__closure__属性中
  • 应用:装饰器、延迟计算、封装私有变量

15. 装饰器:Python 的 AOP 实现

本质:接收一个函数/类,返回增强版的函数/类,满足开闭原则(不修改原代码,动态加功能)

  • 核心场景:日志、权限、缓存、计时

16. 鸭子类型:关注行为而非身份

「走起来像鸭子、叫起来像鸭子,就是鸭子」

  • Python 实践:不需要继承特定接口(如Readable),只要实现read()就可以当文件用
  • 优势:代码更灵活、解耦度更高

17. 下划线命名的约定(非强制)

  • _foo:私有约定,from module import *不导入
  • __foo:名称修饰(变成_ClassName__foo),防止子类覆盖父类属性
  • __foo__:魔术方法,Python 内部定义,开发者不要自定义

18. 元类(简单了解框架即可)

面试常考定义,日常开发少用:

  • 类是创建实例的模板,元类是创建类的模板
  • 默认元类是type
  • 应用:Django ORM、状态机检查(类定义时自动修改行为)

六、补充高频零散考点

19. *args/**kwargs:灵活的参数解包

  • *args:接收任意位置参数,存为元组
  • **kwargs:接收任意关键字参数,存为字典
  • 进阶用法:函数调用时解包列表/字典为参数
    def add(a,b,c): return a+b+c
    print(add(*[1,2,3]))  # 输出6

参数顺序(定义时):普通→*args→默认→**kwargs

20. 列表/字典推导式

现代 Python 优先用推导式,比map/filter更直观:

  • 列表推导式:[x*2 for x in range(5) if x>2]
  • 字典推导式(Py2.7+):{v:k for k,v in {'a':1,'b':2}.items()}

21. 为什么 Python 不需要函数重载?

动态语言特性天然覆盖:

  1. 参数类型:无需声明,可接收任意类型
  2. 参数个数:用默认参数、*args/**kwargs解决

全文完,剩余中高级特性(如 GIL、协程、单例模式)可单独成篇深入探讨。