Skip to content

Python 基础面试题 ​

list 和 tuple 有什么区别? ​

难度:简单 | 标签:数据类型, list, tuple

  • 可变性:list 可变,可以增删改元素;tuple 不可变,创建后不能修改元素。
  • 语法:list 使用 [],tuple 使用 ();单元素 tuple 需要写成 (1,)。
  • 性能:tuple 结构更紧凑,创建和访问略快,占用内存更少。
  • 哈希:只有元素都可哈希的 tuple 才能作为 dict 的 key 或 set 的元素,list 不行。
  • 语义:list 表示一组同类型、可变的序列;tuple 通常表示一条不可变的结构化记录。
python
point = (3, 4)
point[0] = 5  # TypeError: 'tuple' object does not support item assignment

numbers = [1, 2, 3]
numbers.append(4)  # [1, 2, 3, 4]

注意:tuple 的「不可变」是浅层的,如果 tuple 里存放的是 list,这个 list 本身仍然可以被修改。

什么是 GIL?它对多线程有什么影响? ​

难度:中等 | 标签:GIL, 并发, 多线程

GIL(Global Interpreter Lock,全局解释器锁)是 CPython 解释器中的一把互斥锁。同一时刻只允许一个线程执行 Python 字节码,因此多线程无法真正利用多核 CPU 并行执行 Python 代码。

影响:

  • CPU 密集型任务:多线程受 GIL 限制,无法提速,应使用多进程(multiprocessing);
  • I/O 密集型任务:线程在等待 I/O 时会释放 GIL,多线程仍然有效,也可以使用 asyncio;
  • 扩展模块:NumPy 等 C 扩展在执行计算时会释放 GIL,因此能获得一定并行能力。

需要强调:GIL 是 CPython 的实现细节,不是 Python 语言规范。Jython、IronPython 没有 GIL;Python 3.13 开始也提供了实验性的 free-threaded 构建(PEP 703)。

Python 装饰器的原理是什么? ​

难度:中等 | 标签:装饰器, 闭包, 高阶函数

装饰器本质上是一个接收函数并返回新函数的高阶函数,常用于在不修改原函数代码的前提下增加日志、缓存、权限校验等能力。它依赖闭包保存原函数。

python
import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} 耗时 {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timer
def slow_task(n):
    time.sleep(n)

# 等价于 slow_task = timer(slow_task)

要点:

  • @functools.wraps(func) 会保留原函数的 __name__、__doc__ 等元信息,排查问题更方便;
  • 带参数的装饰器需要再包一层:def repeat(times): def decorator(func): ... return decorator;
  • 多个装饰器从下往上依次包装,执行时从上往下进入。

生成器和迭代器有什么区别? ​

难度:中等 | 标签:生成器, 迭代器, 惰性求值

  • 迭代器:实现了 __iter__() 和 __next__() 协议的对象,能被 for 循环遍历;__next__() 每次返回一个元素,耗尽后抛出 StopIteration。
  • 生成器:一种用 yield 快速创建迭代器的语法糖(也可以是生成器表达式),自动实现了迭代器协议,并且是惰性求值的。
python
def read_lines(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.strip()

# 不会一次性把整个文件读进内存
for line in read_lines("big.log"):
    print(line)

区别在于:迭代器是协议,生成器是实现方式之一;生成器函数调用后不会立即执行,而是在每次 next() 时运行到下一个 yield,适合处理大文件、数据流等内存敏感场景。

Python 的深拷贝和浅拷贝有什么区别? ​

难度:简单 | 标签:拷贝, copy, 引用

  • 赋值:只是给同一个对象增加一个引用,不产生新对象;
  • 浅拷贝(copy.copy、切片、list()):创建新的外层容器,但内部元素仍然是原对象中元素的引用;
  • 深拷贝(copy.deepcopy):递归复制所有层级,得到的对象与原对象完全独立。
python
import copy

a = [[1, 2], [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)

b[0][0] = 99
print(a)  # [[99, 2], [3, 4]],浅拷贝共享内层列表

c[1][1] = 100
print(a)  # [[99, 2], [3, 4]],深拷贝互不影响

deepcopy 会维护一个 memo 字典处理循环引用,因此性能开销更大;对于不可变对象(如 int、str、tuple 中的不可变元素),deepcopy 通常会直接返回原对象。

asyncio 的协程为什么适合 I/O 密集型任务? ​

难度:中等 | 标签:asyncio, 协程, 异步编程

asyncio 基于事件循环,在单线程内通过协作式调度实现并发。协程遇到 await 的 I/O 操作时会主动让出控制权,事件循环转而执行其他就绪任务,等 I/O 完成后再恢复执行,从而避免线程阻塞和上下文切换开销。

python
import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        pages = await asyncio.gather(
            fetch(session, "https://example.com/a"),
            fetch(session, "https://example.com/b"),
        )
    return pages

asyncio.run(main())

适用场景:网络请求、数据库访问、文件 I/O 等等待时间远大于计算时间的任务。不适合 CPU 密集型任务,因为协程无法绕过 GIL,计算仍然会阻塞事件循环;这类任务应交给多进程或线程池。