Python面试题¶
40道Python面试高频题 + 详细解答,覆盖基础概念、高级特性、并发编程、实战问题等核心知识点。
一、基础概念(10题)¶
1. Python是解释型语言还是编译型语言?¶
Python既有编译过程,也有解释过程。 严格来说是"先编译后解释"的混合型。
执行过程:
- 编译阶段:Python源码被编译为字节码(.pyc文件),这是一种中间代码,不是机器码
- 解释阶段:Python虚拟机(PVM/解释器)逐条解释执行字节码
与真正的编译型语言的区别: - C/C++:源码 → 编译 → 机器码 → 直接运行(速度快) - Java:源码 → 编译 → 字节码 → JVM执行(JIT可编译为机器码) - Python:源码 → 编译 → 字节码 → PVM解释执行(CPython 3.13起实验性支持JIT,相对较慢)
查看字节码:
import dis
def add(a, b):
return a + b
dis.dis(add)
# 输出:
# LOAD_FAST 0 (a)
# LOAD_FAST 1 (b)
# BINARY_ADD
# RETURN_VALUE
# 注意:Python 3.11+ 使用 BINARY_OP 替代了 BINARY_ADD/BINARY_SUBTRACT 等独立指令。
Python的实现版本: - CPython:官方实现,C语言编写的解释器(最常用) - PyPy:带JIT编译的实现,速度更快 - Jython:运行在JVM上 - IronPython:运行在.NET上
2. Python中可变类型和不可变类型有哪些?¶
不可变类型(Immutable): - int、float、bool、str、tuple、frozenset、bytes
可变类型(Mutable): - list、dict、set、bytearray
不可变类型的特点:
a = "hello"
print(id(a)) # 140234567890
a = a + " world" # 创建了新对象
print(id(a)) # 140234567999 (id变了!)
# 字符串是不可变的,"修改"实际上是创建新对象
可变类型的特点:
lst = [1, 2, 3]
print(id(lst)) # 140234567890
lst.append(4) # 在原对象上修改
print(id(lst)) # 140234567890 (id不变!)
函数传参的影响:
def modify(x, lst):
x = x + 1 # 不可变类型: 创建新对象, 不影响外部
lst.append(4) # 可变类型: 修改原对象, 影响外部
a = 10
b = [1, 2, 3]
modify(a, b)
print(a) # 10 (没变)
print(b) # [1, 2, 3, 4] (被修改了)
面试关键点: Python中的参数传递是"传对象引用"(Pass by Object Reference),不可变对象的修改会创建新对象,可变对象的修改是就地修改。
3. 深拷贝和浅拷贝有什么区别?¶
赋值(=): 不拷贝,只是引用同一个对象
浅拷贝(Shallow Copy): 创建新的外层对象,但内层对象仍是引用
import copy
a = [1, [2, 3]]
b = copy.copy(a) # 浅拷贝 (也可以: b = a[:] 或 b = list(a))
# a: [1, [2, 3]] id(a) = 100
# ↑ ↑
# 1 [2,3] id([2,3]) = 200
# ↑ ↑
# b: [1, [2, 3]] id(b) = 300 (新外层对象)
b[0] = 99 # 修改不可变元素 → 不影响a
print(a) # [1, [2, 3]] ✅
b[1].append(4) # 修改可变元素(内层引用) → 影响a
print(a) # [1, [2, 3, 4]] ❌ a也变了!
深拷贝(Deep Copy): 递归地创建所有层级的新对象
a = [1, [2, 3]]
b = copy.deepcopy(a) # 深拷贝
b[1].append(4)
print(a) # [1, [2, 3]] ✅ a没变
print(b) # [1, [2, 3, 4]]
图解对比:
赋值: a ──→ [1, [2,3]]
b ──↗
浅拷贝: a ──→ [1, ──→ [2,3]]
b ──→ [1, ──↗ ] (外层不同, 内层共享)
深拷贝: a ──→ [1, ──→ [2,3]]
b ──→ [1, ──→ [2,3]] (完全独立)
浅拷贝的方式:
# 列表
b = a[:] # 切片操作:[start:end:step]提取子序列
b = list(a)
b = a.copy()
b = copy.copy(a)
# 字典
b = dict(a)
b = a.copy()
b = {**a}
4. is 和 == 有什么区别?¶
# == 比较值是否相等
# is 比较是否是同一个对象(内存地址/id是否相同)
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True (值相等)
print(a is b) # False (不同对象)
c = a
print(a is c) # True (同一对象)
Python的小整数缓存池:
# Python缓存了-5到256的整数
a = 256
b = 256
print(a is b) # True (缓存池中同一个对象)
a = 257
b = 257
print(a is b) # False (超出缓存范围, 不同对象)
# 注意: 在交互式环境和脚本中行为可能不同
字符串驻留(intern):
a = "hello"
b = "hello"
print(a is b) # True (短字符串驻留)
a = "hello world!"
b = "hello world!"
print(a is b) # 不一定 (含特殊字符可能不驻留)
面试关键点: - == 调用 __eq__ 方法 - is 比较 id() - None 的比较推荐用 is:if x is None
5. args 和 *kwargs 是什么?¶
# *args: 接收任意数量的位置参数, 打包成元组(tuple)
# **kwargs: 接收任意数量的关键字参数, 打包成字典(dict)
def func(*args, **kwargs):
print(f"args: {args}") # 元组
print(f"kwargs: {kwargs}") # 字典
func(1, 2, 3, name="张三", age=25)
# args: (1, 2, 3)
# kwargs: {'name': '张三', 'age': 25}
参数顺序:
解包操作:
# * 解包可迭代对象
lst = [1, 2, 3]
print(*lst) # 1 2 3
# ** 解包字典
d = {"name": "张三", "age": 25}
def greet(name, age):
print(f"{name}, {age}")
greet(**d) # 张三, 25
# 合并字典
d1 = {"a": 1}
d2 = {"b": 2}
merged = {**d1, **d2} # {"a": 1, "b": 2}
# 合并列表
l1 = [1, 2]
l2 = [3, 4]
merged = [*l1, *l2] # [1, 2, 3, 4]
6. Python的内存管理机制是什么?¶
Python内存管理由三个机制组成:
1. 引用计数(Reference Counting)— 主要机制
import sys
a = [1, 2, 3] # 引用计数 = 1
b = a # 引用计数 = 2
print(sys.getrefcount(a)) # 3 (传参时临时+1)
del b # 引用计数 = 1
del a # 引用计数 = 0 → 立即释放内存
引用计数增加的情况: - 对象创建:a = "hello" - 引用赋值:b = a - 作为参数传递:func(a) - 加入容器:lst.append(a)
引用计数减少的情况: - del a - 变量被重新赋值 - 离开作用域 - 从容器中移除
引用计数的问题 — 循环引用:
a = []
b = []
a.append(b) # a引用b
b.append(a) # b引用a
del a
del b
# 虽然删除了变量,但a和b的引用计数各为1(互相引用)
# 引用计数无法回收 → 需要垃圾回收器处理
2. 标记清除(Mark and Sweep) - 解决循环引用问题 - 从GC Roots(全局变量、栈上变量等)开始遍历 - 可达的对象标记为存活,不可达的回收
3. 分代回收(Generational Collection)
第0代(Generation 0): 新创建的对象, 检查最频繁
↓ 存活
第1代(Generation 1): 经历过一次GC仍存活, 检查较少
↓ 存活
第2代(Generation 2): 长期存活的对象, 检查最少
- 新对象放入第0代
- 每次GC后存活的对象晋升到下一代
- 高代的检查频率低(长期存活的对象大概率会继续存活)
import gc
gc.get_threshold() # (700, 10, 10) 默认阈值
# 第0代: 分配-释放 > 700次 触发GC
# 第1代: 第0代GC 10次后触发
# 第2代: 第1代GC 10次后触发
gc.collect() # 手动触发完整GC
gc.get_count() # 查看各代计数
7. Python的垃圾回收机制如何手动控制?¶
import gc
# 查看GC状态
gc.isenabled() # 是否启用
gc.get_threshold() # GC触发阈值
# 手动控制
gc.disable() # 禁用自动GC(高性能场景)
gc.enable() # 启用自动GC
gc.collect() # 手动触发GC,返回不可达对象数量
# 调整阈值
gc.set_threshold(700, 10, 10)
# 查找循环引用
gc.set_debug(gc.DEBUG_LEAK) # 打印泄漏信息
weakref — 弱引用:
import weakref
class MyClass:
pass
obj = MyClass()
weak_ref = weakref.ref(obj) # 弱引用不增加引用计数
print(weak_ref()) # <MyClass object>
del obj
print(weak_ref()) # None (对象已被回收)
弱引用常用于缓存、观察者模式等场景,避免循环引用导致内存泄漏。
8. Python中的命名空间和作用域是什么?¶
四种作用域(LEGB规则):
L - Local: 函数内部局部作用域
E - Enclosing: 闭包外层函数的作用域
G - Global: 模块级别的全局作用域
B - Built-in: Python内置作用域
x = "global" # Global
def outer():
x = "enclosing" # Enclosing
def inner():
x = "local" # Local
print(x) # 查找顺序: L → E → G → B
inner()
outer() # 输出: local
global 和 nonlocal 关键字:
x = 10
def func():
global x # 声明使用全局变量x
x = 20
func()
print(x) # 20
def outer():
x = 10
def inner():
nonlocal x # 声明使用外层函数的x
x = 20
inner()
print(x) # 20
outer()
9. Python中的闭包是什么?¶
闭包(Closure): 函数嵌套中,内层函数引用了外层函数的变量,并且外层函数返回内层函数。
def make_counter():
count = 0 # 外层变量
def counter():
nonlocal count
count += 1 # 引用外层变量
return count
return counter # 返回内层函数
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
# count变量在make_counter返回后仍然存活(被闭包引用)
闭包的常见陷阱 — 延迟绑定:
# 经典问题
functions = []
for i in range(5):
def func():
return i
functions.append(func)
print([f() for f in functions])
# [4, 4, 4, 4, 4] 不是 [0, 1, 2, 3, 4]!
# 因为闭包引用的是变量i,循环结束后i=4
# 解决方案1:使用默认参数
functions = []
for i in range(5):
def func(x=i): # 默认参数在定义时求值
return x
functions.append(func)
print([f() for f in functions]) # [0, 1, 2, 3, 4]
# 解决方案2:使用functools.partial
from functools import partial
functions = [partial(lambda x: x, i) for i in range(5)]
10. Python的魔术方法 __new__ 和 __init__ 有什么区别?¶
class MyClass:
def __new__(cls, *args, **kwargs):
print("__new__ called")
instance = super().__new__(cls) # 创建实例
return instance # 必须返回实例
def __init__(self, name):
print("__init__ called")
self.name = name # 初始化实例
obj = MyClass("张三")
# 输出:
# __new__ called
# __init__ called
| 对比 | __new__ | __init__ |
|---|---|---|
| 作用 | 创建实例(构造器) | 初始化实例(初始化器) |
| 参数 | cls(类本身) | self(实例本身) |
| 返回值 | 必须返回实例 | 不返回(None) |
| 调用时机 | 先调用 | 后调用(__new__返回实例后) |
| 类型 | 静态方法 | 实例方法 |
| 用途 | 单例模式、不可变类型子类化 | 属性赋值 |
__new__ 的典型应用 — 单例模式:
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
a = Singleton()
b = Singleton()
print(a is b) # True (同一个实例)
二、高级特性(10题)¶
11. Python装饰器的原理是什么?如何实现参数装饰器和类装饰器?¶
装饰器的本质: 接收一个函数,返回一个新函数(或修改过的函数)。
基础装饰器:
def timer(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 耗时: {end - start:.4f}s")
return result
return wrapper
@timer # 等价于: my_func = timer(my_func)
def my_func():
time.sleep(1)
my_func() # my_func 耗时: 1.0012s
functools.wraps的作用:
from functools import wraps
def timer(func):
@wraps(func) # 保留原函数的元信息(__name__, __doc__等)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# 不用@wraps: my_func.__name__ = 'wrapper' ❌
# 使用@wraps: my_func.__name__ = 'my_func' ✅
带参数的装饰器(三层嵌套):
def retry(max_retries=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if i == max_retries - 1:
raise
print(f"重试 {i+1}/{max_retries}: {e}")
time.sleep(delay)
return wrapper
return decorator
@retry(max_retries=5, delay=2) # 等价于: func = retry(5, 2)(func)
def fetch_data():
pass
类装饰器:
class CacheDecorator:
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if args in self.cache:
return self.cache[args]
result = self.func(*args)
self.cache[args] = result
return result
@CacheDecorator
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # 55 (有缓存加速)
用装饰器装饰类(类作为被装饰对象):
def singleton(cls):
instances = {}
@wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
pass
多个装饰器的执行顺序:
@decorator_a
@decorator_b
def func():
pass
# 等价于: func = decorator_a(decorator_b(func))
# 装饰顺序: 从下到上
# 执行顺序: 从上到下
12. 生成器和迭代器有什么区别?yield的原理是什么?¶
迭代器(Iterator): - 实现了 __iter__() 和 __next__() 方法的对象 - 可以用 next() 逐个获取元素
class CountDown:
def __init__(self, n):
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.n <= 0:
raise StopIteration
self.n -= 1
return self.n + 1
for num in CountDown(5):
print(num) # 5, 4, 3, 2, 1
生成器(Generator): - 使用 yield 的函数,调用后返回生成器对象 - 是迭代器的简洁写法 - 惰性计算:不一次性生成所有数据,而是按需生成
def countdown(n):
while n > 0:
yield n # 暂停并返回值
n -= 1 # 下次从这里继续
gen = countdown(5)
print(next(gen)) # 5
print(next(gen)) # 4
yield的原理: - yield 暂停函数执行,保存函数的状态(局部变量、指令位置) - next() 恢复函数执行,从上次 yield 的位置继续 - 函数体执行完毕后抛出 StopIteration
send() 方法:
def accumulator():
total = 0
while True:
value = yield total # yield返回total, 同时接收send的值
total += value
gen = accumulator()
next(gen) # 0 (初始化,必须先next一次)
print(gen.send(10)) # 10
print(gen.send(20)) # 30
print(gen.send(30)) # 60
yield from — 委托生成器:
def sub_gen():
yield 1
yield 2
def main_gen():
yield from sub_gen() # 等价于: for x in sub_gen(): yield x
yield 3
list(main_gen()) # [1, 2, 3]
生成器表达式:
# 列表推导 → 立即生成所有元素
lst = [x**2 for x in range(1000000)] # 占用大量内存
# 生成器表达式 → 惰性计算
gen = (x**2 for x in range(1000000)) # 几乎不占内存
13. 上下文管理器是什么?如何实现?¶
上下文管理器: 用 with 语句管理资源的获取和释放。
方式1:实现 __enter__ 和 __exit__ 方法
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file # with ... as f 中f的值
def __exit__(self, exc_type, exc_val, exc_tb):
# exc_type: 异常类型, exc_val: 异常值, exc_tb: traceback
if self.file:
self.file.close()
return False # True表示异常已处理, 不再抛出
with FileManager("test.txt", "w") as f:
f.write("hello")
# 离开with块时自动调用__exit__, 关闭文件
方式2:使用 contextmanager 装饰器
from contextlib import contextmanager
@contextmanager
def file_manager(filename, mode):
f = open(filename, mode)
try:
yield f # yield之前是__enter__, yield的值是as后的变量
finally:
f.close() # yield之后是__exit__
with file_manager("test.txt", "w") as f:
f.write("hello")
实际应用场景:
# 数据库连接
@contextmanager
def db_connection():
conn = create_connection()
try:
yield conn
conn.commit() # 没异常则提交
except Exception:
conn.rollback() # 有异常则回滚
raise
finally:
conn.close() # 总是关闭连接
# 计时器
@contextmanager
def timer(label):
start = time.time()
yield
print(f"{label}: {time.time() - start:.4f}s")
with timer("处理数据"):
process_data()
# 临时修改工作目录
@contextmanager
def working_directory(path):
old_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(old_dir)
14. Python的元类(metaclass)是什么?¶
元类: 类的类。类是元类的实例。
# 普通对象是类的实例
# 类是元类的实例
# 默认元类是 type
class MyClass:
pass
print(type(MyClass)) # <class 'type'>
print(type(type)) # <class 'type'> (type的元类是自身)
type动态创建类:
# 以下两种方式等价:
# 方式1: class语句
class Dog:
sound = "woof"
def speak(self):
return self.sound
# 方式2: type()
Dog = type("Dog", (object,), {
"sound": "woof",
"speak": lambda self: self.sound
})
自定义元类:
class MyMeta(type):
def __new__(mcs, name, bases, namespace):
# 在类创建时执行
print(f"Creating class: {name}")
# 强制所有方法名小写
for key in list(namespace.keys()):
if callable(namespace[key]) and not key.startswith('_'):
if key != key.lower():
raise ValueError(f"方法名必须是小写: {key}")
return super().__new__(mcs, name, bases, namespace)
class MyClass(metaclass=MyMeta):
def my_method(self): # ✅
pass
# def MyMethod(self): # ❌ ValueError
# pass
ORM示例 — 元类的实际应用:
class ModelMeta(type):
def __new__(mcs, name, bases, namespace):
if name == 'Model': # 跳过基类
return super().__new__(mcs, name, bases, namespace)
# 收集字段定义
fields = {}
for key, value in namespace.items():
if isinstance(value, Field):
fields[key] = value
namespace['_fields'] = fields
namespace['_table_name'] = name.lower() + 's'
return super().__new__(mcs, name, bases, namespace)
class Field:
def __init__(self, field_type):
self.field_type = field_type
class Model(metaclass=ModelMeta):
def save(self):
fields = ', '.join(self._fields.keys())
print(f"INSERT INTO {self._table_name} ({fields}) ...")
class User(Model):
name = Field("VARCHAR(100)")
age = Field("INT")
user = User()
user.save() # INSERT INTO users (name, age) ...
15. 什么是描述符协议?¶
描述符(Descriptor): 实现了 __get__、__set__、__delete__ 中任意一个方法的类。
class TypedField:
"""类型检查描述符"""
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
if not isinstance(value, self.expected_type):
raise TypeError(f"{self.name}必须是{self.expected_type}")
obj.__dict__[self.name] = value
def __delete__(self, obj):
del obj.__dict__[self.name]
class Person:
name = TypedField("name", str)
age = TypedField("age", int)
def __init__(self, name, age):
self.name = name # 触发 __set__
self.age = age
p = Person("张三", 25) # ✅
# p = Person("张三", "25") # ❌ TypeError
描述符的分类: - 数据描述符:实现了 __get__ 和 __set__(优先级最高) - 非数据描述符:只实现了 __get__(如函数/方法)
Python内置的描述符应用: - property(属性) - staticmethod(静态方法) - classmethod(类方法) - 函数(function.__get__ 实现了方法绑定)
16. property装饰器的原理和用法是什么?¶
class Circle:
def __init__(self, radius):
self._radius = radius
@property # @property将方法变为属性访问
def radius(self):
"""getter"""
return self._radius
@radius.setter
def radius(self, value):
"""setter"""
if value < 0:
raise ValueError("半径不能为负数")
self._radius = value
@radius.deleter
def radius(self):
"""deleter"""
del self._radius
@property
def area(self):
"""只读属性(计算属性)"""
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.radius) # 5 (调用getter)
c.radius = 10 # 调用setter
print(c.area) # 314.159 (只读计算属性)
# c.area = 100 # ❌ AttributeError (没有setter)
property的底层是描述符:
# @property 等价于:
class Circle:
def get_radius(self):
return self._radius
def set_radius(self, value):
self._radius = value
radius = property(get_radius, set_radius)
17. Python中如何实现抽象类和接口?¶
from abc import ABC, abstractmethod # ABC抽象基类;abstractmethod强制子类实现
class Shape(ABC): # 抽象基类
@abstractmethod
def area(self):
"""子类必须实现"""
pass
@abstractmethod
def perimeter(self):
pass
def description(self):
"""普通方法,子类可以直接使用"""
return f"这是一个{self.__class__.__name__}"
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self): # 必须实现
return 3.14 * self.radius ** 2
def perimeter(self): # 必须实现
return 2 * 3.14 * self.radius
# shape = Shape() # ❌ TypeError: 不能实例化抽象类
circle = Circle(5) # ✅
18. __slots__ 的作用和原理是什么?¶
class WithoutSlots:
def __init__(self, x, y):
self.x = x
self.y = y
class WithSlots:
__slots__ = ('x', 'y') # 限制实例只能有x和y属性
def __init__(self, x, y):
self.x = x
self.y = y
# 不用__slots__: 使用__dict__存储属性
a = WithoutSlots(1, 2)
a.z = 3 # ✅ 可以动态添加属性
print(a.__dict__) # {'x': 1, 'y': 2, 'z': 3}
# 使用__slots__: 不使用__dict__, 使用更紧凑的内部结构
b = WithSlots(1, 2)
# b.z = 3 # ❌ AttributeError
# b.__dict__ # ❌ 没有__dict__
__slots__ 的优点: 1. 节省内存:不再使用 __dict__,每个实例可以节省约40-50%内存 2. 访问速度更快:属性存储使用固定的偏移量 3. 限制属性:防止意外添加属性
适用场景: 需要创建大量实例的类(如数据对象、ORM实体)
19. 协程是什么?async/await的原理是什么?¶
协程(Coroutine): 可以在执行过程中暂停和恢复的函数,适用于IO密集型的并发编程。
import asyncio
async def fetch_data(url):
print(f"开始请求 {url}")
await asyncio.sleep(1) # 模拟IO操作,让出控制权
print(f"请求完成 {url}")
return f"数据: {url}"
async def main():
# 并发执行多个协程
tasks = [
asyncio.create_task(fetch_data("url1")),
asyncio.create_task(fetch_data("url2")),
asyncio.create_task(fetch_data("url3")),
]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())
# 三个请求并发执行,总耗时约1秒而非3秒
async/await的本质: - async def 定义协程函数,返回协程对象 - await 暂停当前协程,等待另一个协程完成 - 协程在单线程中运行,通过事件循环(Event Loop)调度 - 遇到IO等待时让出CPU,执行其他协程
20. Python中的设计模式 — 观察者模式实现¶
class EventEmitter:
def __init__(self):
self._listeners = {}
def on(self, event, callback):
"""注册监听器"""
if event not in self._listeners:
self._listeners[event] = []
self._listeners[event].append(callback)
def emit(self, event, *args, **kwargs):
"""触发事件"""
for callback in self._listeners.get(event, []):
callback(*args, **kwargs)
def off(self, event, callback):
"""移除监听器"""
if event in self._listeners:
self._listeners[event].remove(callback)
# 使用
emitter = EventEmitter()
def on_user_login(user):
print(f"发送欢迎邮件给 {user}")
def on_user_login_log(user):
print(f"记录登录日志: {user}")
emitter.on("login", on_user_login)
emitter.on("login", on_user_login_log)
emitter.emit("login", "张三")
# 发送欢迎邮件给 张三
# 记录登录日志: 张三
三、并发编程(10题)¶
21. Python的GIL是什么?它有什么影响?如何规避?¶
GIL(Global Interpreter Lock,全局解释器锁): CPython解释器中的一个互斥锁,确保同一时刻只有一个线程执行Python字节码。
为什么有GIL? - CPython的内存管理(引用计数)不是线程安全的 - GIL是最简单的保护方式 - 历史原因:很多C扩展依赖GIL
GIL的影响: - CPU密集型任务:多线程无法利用多核,性能约等于单线程 - IO密集型任务:线程在等IO时会释放GIL,影响较小
# CPU密集型: 多线程反而更慢(GIL + 线程切换开销)
import threading
import time
def cpu_bound():
count = 0
for _ in range(10**7):
count += 1
# 单线程
start = time.time()
cpu_bound()
cpu_bound()
print(f"单线程: {time.time()-start:.2f}s") # ~1.5s
# 多线程
start = time.time()
t1 = threading.Thread(target=cpu_bound)
t2 = threading.Thread(target=cpu_bound)
t1.start(); t2.start()
t1.join(); t2.join()
print(f"多线程: {time.time()-start:.2f}s") # ~1.5s (没有加速!)
规避GIL的方法:
-
多进程(multiprocessing):每个进程有自己的GIL
-
C扩展:Numpy、Pandas等C实现的库在计算时会释放GIL
-
异步IO(asyncio):IO密集型任务的最佳选择
-
使用其他解释器:PyPy、Jython(无GIL)
-
Python 3.13+ 自由线程模式:实验性的无GIL模式
22. 多线程、多进程和协程的区别是什么?各自的适用场景?¶
| 维度 | 多线程 | 多进程 | 协程 |
|---|---|---|---|
| 并发模型 | 抢占式 | 抢占式 | 协作式 |
| 切换开销 | 中等 | 大(需要切换进程上下文) | 小(用户态切换) |
| 内存共享 | 共享(需要加锁) | 不共享(需要IPC) | 共享(单线程) |
| GIL影响 | 受限 | 不受限 | 不涉及 |
| 适用场景 | IO密集型 | CPU密集型 | IO密集型(高并发) |
| 资源消耗 | 中 | 高 | 低 |
| 创建数量 | 百级 | 十级 | 万级 |
| 编程复杂度 | 中(需要锁) | 中(需要IPC) | 低(async/await) |
适用场景总结: - 多线程:爬虫(IO等待多)、文件读写、网络请求 - 多进程:数据分析(CPU计算密集)、图像处理、科学计算 - 协程:高并发网络服务(Web服务器)、大量API调用
23. threading模块的同步原语有哪些?¶
1. Lock(互斥锁)
import threading
lock = threading.Lock()
counter = 0
def increment():
global counter
for _ in range(100000):
lock.acquire() # 加锁
try:
counter += 1
finally:
lock.release() # 释放锁
# 简洁写法
def increment():
global counter
for _ in range(100000):
with lock: # 自动加锁和释放
counter += 1
2. RLock(可重入锁)
rlock = threading.RLock()
def recursive_func(n):
with rlock: # 同一线程可以多次获取
if n > 0:
recursive_func(n - 1)
3. Semaphore(信号量)
# 限制并发数
sem = threading.Semaphore(3) # 最多3个线程同时执行
def worker():
with sem:
print(f"{threading.current_thread().name} 开始工作")
time.sleep(1)
4. Event(事件)
event = threading.Event()
def waiter():
print("等待事件...")
event.wait() # 阻塞等待
print("事件触发!")
def setter():
time.sleep(2)
event.set() # 触发事件
5. Condition(条件变量)
condition = threading.Condition()
items = []
def producer():
with condition:
items.append("item")
condition.notify() # 通知消费者
def consumer():
with condition:
while not items:
condition.wait() # 等待通知
item = items.pop()
24. multiprocessing模块怎么使用?¶
进程池(Pool):
from multiprocessing import Pool
def process_data(x):
return x ** 2
# 进程池
with Pool(processes=4) as pool:
# map: 并行映射
results = pool.map(process_data, range(100))
# apply_async: 异步提交单个任务
future = pool.apply_async(process_data, (42,))
result = future.get(timeout=10)
# starmap: 多参数映射
results = pool.starmap(func, [(1, 2), (3, 4), (5, 6)])
进程间通信:
from multiprocessing import Process, Queue, Pipe
# Queue
queue = Queue()
def producer(q):
q.put("hello")
def consumer(q):
msg = q.get()
print(msg)
# Pipe
parent_conn, child_conn = Pipe()
def sender(conn):
conn.send("message")
conn.close()
def receiver(conn):
msg = conn.recv()
print(msg)
共享内存:
from multiprocessing import Value, Array
counter = Value('i', 0) # 共享整数
arr = Array('d', [0.0] * 10) # 共享数组
def increment(counter):
with counter.get_lock():
counter.value += 1
25. asyncio的事件循环和异步编程¶
import asyncio
import aiohttp
# 异步HTTP请求
async def fetch_url(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3",
]
async with aiohttp.ClientSession() as session:
# 并发执行所有请求
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
# 或者使用 as_completed 按完成顺序处理
for coro in asyncio.as_completed(tasks):
result = await coro
print(result)
asyncio.run(main()) # asyncio.run()启动异步事件循环
异步迭代器和异步生成器:
# 异步生成器
async def async_range(n):
for i in range(n):
await asyncio.sleep(0.1)
yield i # yield生成器:惰性产出值,节省内存
# 异步迭代
async def main():
async for num in async_range(10):
print(num)
异步上下文管理器:
class AsyncDB:
async def __aenter__(self):
self.conn = await create_connection()
return self.conn
async def __aexit__(self, exc_type, exc, tb):
await self.conn.close()
async def main():
async with AsyncDB() as conn:
await conn.execute("SELECT ...")
26. concurrent.futures 模块的使用¶
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
# 线程池
def fetch_data(url):
import requests
return requests.get(url).text
urls = ["url1", "url2", "url3", "url4"]
with ThreadPoolExecutor(max_workers=4) as executor:
# 方式1: map (结果按提交顺序返回)
results = list(executor.map(fetch_data, urls))
# 方式2: submit + as_completed (结果按完成顺序返回)
futures = {executor.submit(fetch_data, url): url for url in urls}
for future in as_completed(futures):
url = futures[future]
try:
data = future.result(timeout=10)
print(f"{url}: {len(data)} bytes")
except Exception as e:
print(f"{url}: error {e}")
# 进程池 (用法一样,换成ProcessPoolExecutor)
with ProcessPoolExecutor(max_workers=4) as executor:
results = list(executor.map(cpu_bound_func, data_list))
27. 如何在Python中安全地操作共享资源?¶
import threading
from queue import Queue
from collections import deque
# 方案1: 使用锁
lock = threading.Lock()
shared_data = []
def safe_append(item):
with lock:
shared_data.append(item)
# 方案2: 使用线程安全的Queue
task_queue = Queue()
def producer():
task_queue.put("task1")
def consumer():
task = task_queue.get()
task_queue.task_done()
# 方案3: 使用threading.local()
local = threading.local()
def set_user(name):
local.user = name # 每个线程有独立的user变量
# 方案4: 使用atomic操作
# Python的某些操作是原子的(但不建议依赖):
# list.append(), dict[key]=value (由于GIL)
28. 什么是线程池?如何合理设置线程数?¶
from concurrent.futures import ThreadPoolExecutor
# 创建线程池
pool = ThreadPoolExecutor(max_workers=10)
# 线程数设置建议:
# IO密集型: 线程数 = CPU核心数 * 2 (或更多)
# CPU密集型: 进程数 = CPU核心数
import os
cpu_count = os.cpu_count()
# IO密集型任务
io_pool = ThreadPoolExecutor(max_workers=cpu_count * 2)
# CPU密集型任务(使用进程池)
from concurrent.futures import ProcessPoolExecutor
cpu_pool = ProcessPoolExecutor(max_workers=cpu_count)
29. Python中的死锁问题如何避免?¶
# 死锁示例
lock_a = threading.Lock()
lock_b = threading.Lock()
def thread_1():
with lock_a:
time.sleep(0.1)
with lock_b: # 等待lock_b, 被thread_2持有
pass
def thread_2():
with lock_b:
time.sleep(0.1)
with lock_a: # 等待lock_a, 被thread_1持有
pass
# 避免死锁的方法:
# 方法1: 固定加锁顺序
def thread_1():
with lock_a: # 总是先获取lock_a
with lock_b:
pass
def thread_2():
with lock_a: # 总是先获取lock_a
with lock_b:
pass
# 方法2: 使用超时
acquired = lock_a.acquire(timeout=5)
if not acquired:
# 获取锁超时,处理异常
pass
# 方法3: 使用RLock(可重入锁)避免自锁
# 方法4: 使用with语句(自动释放锁,避免忘记释放)
30. asyncio中的常用模式¶
import asyncio
# 1. 超时控制
async def fetch_with_timeout(): # async def定义异步函数;用await调用
try:
result = await asyncio.wait_for( # await等待异步操作完成
slow_operation(),
timeout=5.0
)
except asyncio.TimeoutError:
print("超时!")
# 2. 限制并发数
async def limited_concurrency():
semaphore = asyncio.Semaphore(10) # 最多10个并发
async def bounded_fetch(url):
async with semaphore:
return await fetch(url)
tasks = [bounded_fetch(url) for url in urls]
return await asyncio.gather(*tasks)
# 3. 异步队列
async def producer_consumer():
queue = asyncio.Queue(maxsize=100)
async def producer():
for i in range(1000):
await queue.put(i)
await queue.put(None) # 结束信号
async def consumer():
while True:
item = await queue.get()
if item is None:
break
await process(item)
await asyncio.gather(producer(), consumer())
# 4. 异步锁
lock = asyncio.Lock()
async def safe_operation():
async with lock:
await critical_section()
四、实战问题(10题)¶
31. 单例模式的5种实现方法¶
方式1:使用模块(最简单,推荐)
# singleton.py
class _Singleton:
def __init__(self):
self.value = None
instance = _Singleton()
# 使用: from singleton import instance
方式2:使用 __new__
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
a = Singleton()
b = Singleton()
print(a is b) # True
方式3:使用装饰器
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
pass
方式4:使用元类
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
pass
方式5:线程安全的单例
import threading # 线程池/多线程:并发执行任务
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs): # *args接收任意位置参数;**kwargs接收任意关键字参数
if cls._instance is None: # 双检锁
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls) # super()调用父类方法
return cls._instance
32. Python性能优化的10个技巧¶
1. 选择合适的数据结构
# 查找: dict/set O(1) vs list O(n)
# 大量查找时用set替代list
items = set(big_list) # 查找O(1)
if item in items: # 远快于 if item in big_list
pass
2. 使用生成器处理大数据
# ❌ 一次性加载
data = [process(line) for line in open("huge_file.txt")]
# ✅ 逐行处理
data = (process(line) for line in open("huge_file.txt"))
3. 利用局部变量
# 局部变量比全局变量快
def process():
local_func = some_module.func # 缓存到局部
for item in data:
local_func(item)
4. 使用内置函数和C实现
# ❌ Python循环
total = 0
for x in range(1000000):
total += x
# ✅ 内置函数 (C实现, 快很多)
total = sum(range(1000000))
5. 字符串拼接用join
# ❌ 循环拼接 (每次创建新字符串)
s = ""
for x in items:
s += str(x)
# ✅ join (一次性拼接)
s = "".join(str(x) for x in items)
6. 使用collections模块
from collections import Counter, defaultdict, deque # defaultdict带默认值的字典,避免KeyError
# Counter: 计数
word_count = Counter(words) # Counter计数器:统计元素出现次数
# defaultdict: 默认值
graph = defaultdict(list)
graph[node].append(neighbor)
# deque: 双端队列 (左端操作O(1))
queue = deque()
queue.appendleft(item) # O(1) vs list.insert(0, item) O(n)
7. 使用lru_cache缓存
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
8. 列表推导优于循环
# ❌
result = []
for x in data:
if x > 0:
result.append(x ** 2)
# ✅ 更快
result = [x ** 2 for x in data if x > 0]
9. 使用numpy处理数值计算
import numpy as np
# ❌ Python循环
result = [x * 2 for x in range(1000000)]
# ✅ numpy向量化 (快10-100倍)
arr = np.arange(1000000)
result = arr * 2
10. 使用性能分析工具
# cProfile
python -m cProfile script.py
# line_profiler
@profile
def my_func():
pass
# memory_profiler
@profile
def my_func():
pass
33. Python常用的20个魔术方法整理¶
| 魔术方法 | 用途 | 触发时机 |
|---|---|---|
__init__ | 初始化 | 创建实例时 |
__new__ | 创建实例 | __init__之前 |
__del__ | 析构 | 对象被销毁时 |
__str__ | 用户友好的字符串 | print()、str() |
__repr__ | 开发者友好的字符串 | repr()、交互式环境 |
__len__ | 长度 | len() |
__getitem__ | 索引取值 | obj[key] |
__setitem__ | 索引赋值 | obj[key] = value |
__delitem__ | 索引删除 | del obj[key] |
__contains__ | 包含 | item in obj |
__iter__ | 迭代 | for x in obj |
__next__ | 下一个元素 | next(obj) |
__call__ | 调用 | obj() |
__eq__ | 等于 | obj == other |
__lt__ | 小于 | obj < other |
__hash__ | 哈希值 | hash(obj)、dict的key |
__bool__ | 布尔值 | if obj、bool(obj) |
__enter__/__exit__ | 上下文管理器 | with obj |
__add__ | 加法 | obj + other |
__slots__ | 限制属性 | 类定义时 |
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
def __len__(self):
return int((self.x**2 + self.y**2) ** 0.5)
def __bool__(self):
return self.x != 0 or self.y != 0
def __call__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # (4, 6)
print(v1 == v2) # False
print(v1(2)) # (6, 8)
print(len(v1)) # 5
34. Python常用内置函数及其用法¶
# map: 对每个元素应用函数
nums = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, nums)) # [1, 4, 9, 16, 25]
# filter: 过滤元素
even = list(filter(lambda x: x % 2 == 0, nums)) # [2, 4]
# reduce: 累积计算
from functools import reduce
total = reduce(lambda a, b: a + b, nums) # 15
product = reduce(lambda a, b: a * b, nums) # 120
# zip: 并行迭代
names = ["Alice", "Bob"]
ages = [25, 30]
for name, age in zip(names, ages):
print(f"{name}: {age}")
# zip_longest: 不等长时填充
from itertools import zip_longest
# enumerate: 带索引迭代
for i, item in enumerate(["a", "b", "c"], start=1): # enumerate同时获取索引和值
print(f"{i}: {item}")
# sorted: 排序 (返回新列表)
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
sorted(students, key=lambda x: x[1], reverse=True)
# [("Bob", 92), ("Alice", 85), ("Charlie", 78)]
# any / all
any([False, True, False]) # True (至少一个为True)
all([True, True, True]) # True (全部为True)
# isinstance / issubclass
isinstance(1, (int, float)) # True
issubclass(bool, int) # True
# getattr / setattr / hasattr
class Obj:
x = 10
getattr(Obj, 'x', None) # 10 # hasattr/getattr/setattr动态操作对象属性
hasattr(Obj, 'y') # False
setattr(Obj, 'y', 20)
35. lambda表达式的高级用法¶
# 基础
square = lambda x: x ** 2 # lambda匿名函数:简洁的单行函数
# 排序key
data = [{"name": "Bob", "age": 30}, {"name": "Alice", "age": 25}]
sorted(data, key=lambda x: x["age"])
# 即时函数
(lambda: print("IIFE"))()
# 条件表达式
classify = lambda x: "正数" if x > 0 else ("零" if x == 0 else "负数")
# 函数工厂
def make_multiplier(n):
return lambda x: x * n
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# 与map/filter组合
result = list(map(lambda x: x.upper(), ["hello", "world"]))
# ["HELLO", "WORLD"]
# 注意:复杂逻辑不应该用lambda,用def更清晰
36. 列表推导 vs 生成器表达式的性能对比¶
import sys
import time
# 列表推导: 立即创建所有元素
start = time.time()
lst = [x**2 for x in range(1000000)]
print(f"列表推导耗时: {time.time()-start:.4f}s")
print(f"列表占用内存: {sys.getsizeof(lst)} bytes") # ~8MB
# 生成器表达式: 惰性计算
start = time.time()
gen = (x**2 for x in range(1000000))
print(f"生成器创建耗时: {time.time()-start:.6f}s") # 几乎为0
print(f"生成器占用内存: {sys.getsizeof(gen)} bytes") # ~120 bytes
# 如果只需要遍历一次: 用生成器
# 如果需要多次访问/索引: 用列表
# 如果数据量大: 用生成器(节省内存)
# 如果需要 len/index/slice: 用列表
其他推导形式:
# 字典推导
d = {k: v for k, v in zip(keys, values)} # zip并行遍历多个可迭代对象
# 集合推导
s = {x % 10 for x in range(100)}
# 嵌套推导
matrix = [[1,2,3], [4,5,6], [7,8,9]]
flat = [x for row in matrix for x in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
37. Python中的类型注解(Type Hints)¶
from typing import List, Dict, Tuple, Optional, Union, Callable, Generator
# 基础类型注解
def greet(name: str) -> str:
return f"Hello, {name}"
# 复杂类型
def process(
data: List[int],
config: Dict[str, str],
coord: Tuple[float, float],
callback: Optional[Callable[[int], bool]] = None,
) -> Union[str, None]:
pass
# Python 3.10+ 简化写法
def process(
data: list[int],
config: dict[str, str],
callback: Callable[[int], bool] | None = None,
) -> str | None:
pass
# TypeVar 泛型
from typing import TypeVar
T = TypeVar('T')
def first(items: list[T]) -> T:
return items[0]
# dataclass
from dataclasses import dataclass
@dataclass # 自动生成__init__等方法
class User:
name: str
age: int
email: str = ""
def greet(self) -> str:
return f"Hi, I'm {self.name}"
38. 常见的Python陷阱和注意事项¶
1. 可变默认参数
# ❌ 危险! 默认参数只创建一次
def append_to(item, lst=[]):
lst.append(item)
return lst
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] 不是 [2]!
# ✅ 使用None
def append_to(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
2. 循环中的闭包(前面已讨论)
3. 整数除法
4. 字符串乘法和列表乘法
# 字符串乘法
"ha" * 3 # "hahaha"
# 列表乘法的陷阱
matrix = [[0] * 3] * 3 # ❌ 三个子列表是同一对象!
matrix[0][0] = 1
print(matrix) # [[1,0,0], [1,0,0], [1,0,0]]
matrix = [[0] * 3 for _ in range(3)] # ✅ 独立的子列表
5. try-except-else-finally
try: # try/except捕获异常
result = do_something()
except ValueError as e:
handle_error(e)
else:
# 没有异常时执行
print(f"成功: {result}")
finally:
# 无论如何都执行
cleanup()
39. Python中的序列化和反序列化¶
import json
import pickle
# JSON序列化(跨语言通用)
data = {"name": "张三", "age": 25, "scores": [90, 85, 92]}
# 序列化为字符串
json_str = json.dumps(data, ensure_ascii=False, indent=2) # json.dumps将Python对象转为JSON字符串
# 序列化到文件
with open("data.json", "w", encoding="utf-8") as f: # with自动管理资源,确保文件正确关闭
json.dump(data, f, ensure_ascii=False)
# 反序列化
data = json.loads(json_str) # json.loads将JSON字符串转为Python对象
with open("data.json", "r") as f:
data = json.load(f)
# 自定义序列化
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def user_encoder(obj):
if isinstance(obj, User): # isinstance检查对象类型
return {"name": obj.name, "age": obj.age, "_type": "User"}
raise TypeError(f"不支持序列化 {type(obj)}")
json.dumps(User("张三", 25), default=user_encoder)
# Pickle序列化(Python专用,支持任意对象)
data = pickle.dumps(complex_object)
obj = pickle.loads(data)
# 注意: pickle不安全,不要反序列化不可信的数据
40. Python项目的最佳实践¶
项目结构:
my_project/
├── src/
│ └── my_package/
│ ├── __init__.py
│ ├── core.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ ├── test_core.py
│ └── test_utils.py
├── docs/
├── pyproject.toml # 项目配置(推荐)
├── requirements.txt # 依赖
├── .gitignore
├── README.md
└── Makefile
代码质量工具:
# 格式化
black . # 代码格式化
isort . # import排序
# 类型检查
mypy src/ # 静态类型检查
# Lint
ruff check . # 快速Lint (替代flake8)
pylint src/ # 详细Lint
# 测试
pytest tests/ -v --cov=src # 测试 + 覆盖率
虚拟环境:
# venv
python -m venv .venv
source .venv/bin/activate
# poetry (推荐)
poetry init
poetry add requests
poetry install
# uv (最新,超快)
uv init
uv add requests
面试答题技巧¶
- 基础题:简洁准确,给出代码示例
- 原理题:讲清底层机制(如GIL、MVCC),画图辅助
- 高级特性:对装饰器、元类等讲清本质(函数也是对象)
- 并发题:对比多线程/多进程/协程的区别,结合GIL分析
- 实战题:结合项目经验,讲解如何选择方案和优化性能
最后更新:2025年