Python2和Python3的区别

Python2 与 Python3 是 Python 语言的两个主要版本,Python2 已于 2020 年停止官方维护,目前主流开发均使用 Python3。以下是核心区别:

1. 语法差异

  • print 语句
    • Python2:print "Hello"(语句)
    • Python3:print("Hello")(函数)
  • 整数除法
    • Python2:3 / 2 结果为 1(向下取整)
    • Python3:3 / 2 结果为 1.5(浮点数),3 // 2 才是 1
  • Unicode 字符串
    • Python2:默认 ASCII 编码,需显式声明 u"中文" 为 Unicode
    • Python3:默认 Unicode 编码,"中文" 直接是 Unicode 字符串

2. 库与内置函数变化

  • 库重命名 / 合并:如 urllib2 合并为 urllib.requestConfigParser 改为 configparser
  • range 函数
    • Python2:range() 返回列表,xrange() 返回迭代器(更省内存)
    • Python3:range() 直接返回迭代器,无 xrange()

3. 异常处理

  • Python2:except Exception, e
  • Python3:except Exception as e(更清晰的语法)

4. 其他特性

  • Python3 新增 nonlocal 关键字,支持嵌套函数修改外层变量
  • Python3 对类型提示(Type Hints)支持更完善
  • Python3 性能整体优于 Python2(如字典、列表操作优化)

with语句和上下文管理器

一、with 语句:简化资源管理的语法糖

with 语句是 Python 用于自动管理资源的语法,核心作用是替代 try-finally 块,确保资源(如文件、数据库连接、锁)在使用后被正确释放,避免资源泄漏。

基本语法
with 上下文管理器 as 变量:
    # 使用资源的代码块
# 离开 with 块后,资源自动释放
常见例子:文件操作
# 传统写法(需手动关闭)
f = open('test.txt', 'r')
try:
    content = f.read()
finally:
    f.close()

# with 写法(自动关闭)
with open('test.txt', 'r') as f:
    content = f.read()

二、上下文管理器:实现 __enter____exit__ 的对象

上下文管理器是实现了两个特殊方法的 Python 对象,with 语句的底层逻辑由这两个方法驱动:

表格

方法 作用
__enter__() 进入 with 块前执行,返回值会赋值给 as 后的变量(可选)。
__exit__(exc_type, exc_val, exc_tb) 离开 with 块时执行(无论是否发生异常),用于资源清理。参数接收异常信息(无异常时均为 None)。
自定义上下文管理器示例

模拟一个简单的 “资源获取 - 释放” 过程:

class MyResource:
    def __enter__(self):
        print("获取资源")
        return "资源对象"  # 返回给 as 后的变量

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("释放资源")
        # 可处理异常:返回 True 表示异常已处理,不向外抛出
        if exc_type:
            print(f"捕获异常:{exc_val}")
            return True

# 使用
with MyResource() as res:
    print(f"使用 {res}")
    # 故意触发异常
    raise ValueError("出错了")

三、更简单的方式:contextlib 模块

对于简单的上下文管理器,无需定义类,可使用 contextlib.contextmanager 装饰器,通过生成器函数实现:

from contextlib import contextmanager

@contextmanager
def my_resource():
    # __enter__ 的逻辑(yield 之前)
    print("获取资源")
    res = "资源对象"
    try:
        yield res  # 返回给 as 后的变量
    finally:
        # __exit__ 的逻辑(yield 之后)
        print("释放资源")

# 使用
with my_resource() as res:
    print(f"使用 {res}")

总结

  • with 语句是语法糖,让资源管理代码更简洁、安全;
  • 上下文管理器的核心是 __enter__(获取资源)和 __exit__(释放资源);
  • 简单场景推荐用 contextlib.contextmanager 快速实现。

GIL全局解释器锁是什么?

一、GIL 是什么?(定义)

GIL(Global Interpreter Lock,全局解释器锁)CPython 解释器(Python 最主流的实现)中的一把互斥锁,它的核心规则是:同一时刻,只有一个线程能在 CPU 上执行 Python 字节码

注意:GIL 是 CPython 的特性,Jython、IronPython 等其他 Python 解释器没有 GIL;PyPy 虽有 GIL,但通过 JIT 编译大幅优化了性能。

二、为什么要有 GIL?(历史原因)

GIL 的存在主要是为了简化 CPython 的内存管理

  1. 非线程安全的引用计数:CPython 用「引用计数」管理内存(对象被引用时计数 + 1,销毁时 - 1,归零时释放)。如果没有 GIL,多线程同时修改引用计数会导致「数据竞争」,造成内存泄漏或对象被错误释放。
  2. 历史遗留:早期 Python 社区选择了「单线程执行 + GIL」的简单方案,后续虽想移除,但因大量 C 扩展依赖 GIL 的线程安全保证,移除成本过高。

三、GIL 的影响(核心考点)

GIL 并不意味着「Python 完全无法并行」,需分场景讨论:

1. CPU 密集型任务(计算为主)
  • 多线程反而更慢:因为 GIL 会在「字节码执行一定数量 / 时间后」强制释放(默认 100 条字节码或 0.005 秒),线程间频繁切换、争夺 GIL 会产生额外开销,导致多线程性能甚至不如单线程。
2. I/O 密集型任务(网络、文件读写为主)
  • 多线程有效:当线程执行 I/O 操作(如 time.sleep()、网络请求、文件读写)时,会主动释放 GIL,其他线程可趁机执行。因此多线程能充分利用 I/O 等待时间,提升效率。

四、如何绕过 GIL?(解决方案)

表格

方案 原理 适用场景
多进程(multiprocessing) 每个进程有独立的解释器和 GIL,进程间并行执行 CPU 密集型任务(如数据计算)
C 扩展(Cython/C) 在 C 代码中手动释放 GIL(如 Py_BEGIN_ALLOW_THREADS),让 C 代码并行 性能瓶颈在 C 扩展的计算任务
异步编程(asyncio) 单线程内通过「协程」切换任务,避免 GIL 争夺(本质是单线程并发) I/O 密集型任务(如爬虫、Web 服务)
使用 PyPy PyPy 虽有 GIL,但通过 JIT 即时编译大幅提升执行速度(比 CPython 快 5-10 倍) 纯 Python 代码的 CPU 密集型任务

什么是Cpython?

一、CPython 是什么?(定义)

CPython 是 Python 语言的官方默认解释器实现,由 C 语言编写,因此得名。我们平时从 python.org 下载安装的 Python 就是 CPython。

二、CPython 的核心特点(面试常考)

  1. GIL 的存在CPython 有全局解释器锁(GIL),同一时刻仅允许一个线程执行 Python 字节码,这是它与其他解释器的核心区别(GIL 的影响可参考之前的回答)。

  2. 执行流程先将 .py 源代码编译为字节码(Bytecode,.pyc 文件),再由 CPython 虚拟机(PVM)逐行解释执行字节码。

  3. C 扩展支持可直接调用 C/C++ 编写的扩展模块(如 NumPy、Pandas 底层均依赖 C 扩展),这是 CPython 生态丰富、性能可优化的关键原因。

  4. 跨平台支持 Windows、Linux、macOS 等主流操作系统,兼容性最好。

三、CPython 的地位

  • 使用最广泛的 Python 解释器,市场占比超 90%;
  • 绝大多数第三方库(如 Django、Flask、TensorFlow)优先支持 CPython;
  • Python 语言的新特性(如类型提示、模式匹配)会率先在 CPython 中实现。

四、与其他常见解释器的对比(加分项)

解释器 实现语言 核心特点 适用场景
CPython C 官方默认、有 GIL、支持 C 扩展 绝大多数日常开发、生产环境
PyPy Python JIT 即时编译、速度快(比 CPython 快 5-10 倍)、C 扩展支持有限 纯 Python 代码的 CPU 密集型任务
Jython Java 运行在 JVM 上、可直接调用 Java 代码 与 Java 生态集成的场景
IronPython C# 运行在 .NET 上、可直接调用 .NET 代码 与 .NET 生态集成的场景

总结

CPython 是 Python 的官方默认实现,用 C 语言编写,有 GIL,支持 C 扩展,是目前使用最广泛、生态最完善的 Python 解释器。

迭代器与生成器

一、迭代器(Iterator)

1. 定义

迭代器是 ** 实现了「迭代器协议」** 的对象,核心是能通过 next() 函数逐个获取元素,直到抛出 StopIteration 异常。

2. 迭代器协议(核心考点)

迭代器必须实现两个特殊方法:

  • __iter__():返回迭代器自身(把可迭代对象转成迭代器,为了兼容 for 循环);
  • __next__():返回下一个元素,没有更多元素时抛出 StopIteration 异常。
3. 简单例子
# 列表是「可迭代对象」(Iterable),但不是迭代器
my_list = [1, 2, 3]
# 用 iter() 把可迭代对象转成迭代器
my_iter = iter(my_list)

# 用 next() 逐个获取元素
print(next(my_iter))  # 输出 1
print(next(my_iter))  # 输出 2
print(next(my_iter))  # 输出 3
print(next(my_iter))  # 抛出 StopIteration 异常
4. 迭代器的特点
  • 惰性计算:不一次性生成所有元素,只在调用 next() 时才计算下一个,省内存(适合处理大数据集);
  • 单向遍历:只能向前逐个获取,不能后退或重置。

二、生成器(Generator)

1. 定义

生成器是特殊的迭代器,无需显式实现 __iter____next__,用更简洁的语法就能创建,核心是 yield 关键字。

2. 两种创建方式(核心考点)
方式一:生成器函数(用 yield

普通函数用 return 返回值,生成器函数用 yield「暂停」函数并返回值,下次调用 next() 时从暂停处继续执行。

经典例子:斐波那契数列

def fib(n):
    a, b = 0, 1
    for _ in range(n):
        yield b  # 暂停并返回 b
        a, b = b, a + b

# 调用生成器函数,返回生成器对象(不会立即执行)
f = fib(5)
print(next(f))  # 输出 1(执行到第一个 yield)
print(next(f))  # 输出 1(从暂停处继续,执行到第二个 yield)
print(next(f))  # 输出 2
print(next(f))  # 输出 3
print(next(f))  # 输出 5
方式二:生成器表达式(类似列表推导式)

把列表推导式的 [] 换成 (),就得到生成器表达式,比生成器函数更简洁。

# 列表推导式:一次性生成所有元素,占内存
list_comp = [i*2 for i in range(5)]
print(list_comp)  # 输出 [0, 2, 4, 6, 8]

# 生成器表达式:惰性计算,省内存
gen_comp = (i*2 for i in range(5))
print(next(gen_comp))  # 输出 0
print(next(gen_comp))  # 输出 2
3. 生成器的特点
  • 完全继承迭代器的「惰性计算、省内存」特点;
  • 代码比自定义迭代器简洁得多
  • yield 会保存函数的执行状态(变量值、执行位置),下次 next() 直接恢复。

三、迭代器 vs 生成器(面试常对比)

维度 迭代器 生成器
实现方式 需显式写 __iter____next__ yield 函数或 () 表达式
代码复杂度 较高(需手动维护状态) 极低(语法简洁)
核心关键字 __iter____next__ yield
关系 生成器是特殊的迭代器 属于迭代器的子集

总结

  • 迭代器是实现了 __iter____next__ 的对象,核心是惰性计算、省内存;
  • 生成器是简化版的迭代器,用 yield 或生成器表达式创建,代码更简洁;
  • 两者都适合处理大数据集或无限序列(如斐波那契数列),避免内存溢出。

装饰器

一、装饰器是什么?(定义)

装饰器是 Python 的一种语法糖,核心作用是在不修改原函数 / 类代码的前提下,为其扩展额外功能(如计时、日志、权限验证、缓存等),完美符合「开闭原则」(对扩展开放,对修改关闭)。

二、装饰器的本质原理(核心考点)

装饰器的本质是「高阶函数 + 闭包」,依赖 Python 中「函数是一等公民」的特性:

  • 高阶函数:接收函数作为参数,或返回函数的函数;
  • 闭包:内层函数引用外层函数的变量,且外层函数返回内层函数。

三、基本语法与实现

1. 不带参数的装饰器
原始写法(不用语法糖)
# 定义装饰器(高阶函数)
def my_decorator(func):
    # 定义包装函数(闭包)
    def wrapper(*args, **kwargs):
        # 扩展功能:原函数执行前
        print("原函数执行前")
        # 调用原函数,接收参数和返回值
        result = func(*args, **kwargs)
        # 扩展功能:原函数执行后
        print("原函数执行后")
        return result
    return wrapper

# 被装饰的函数
def say_hello(name):
    print(f"Hello, {name}")
    return "done"

# 使用装饰器:将原函数传给装饰器,返回包装函数
say_hello = my_decorator(say_hello)
say_hello("Tom")
语法糖写法(@)

@装饰器名 放在原函数定义上方,等价于上面的 say_hello = my_decorator(say_hello)

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("原函数执行前")
        result = func(*args, **kwargs)
        print("原函数执行后")
        return result
    return wrapper

@my_decorator  # 语法糖
def say_hello(name):
    print(f"Hello, {name}")
    return "done"

say_hello("Tom")
2. 保留原函数属性:functools.wraps

装饰器会让原函数的 __name____doc__ 等属性被包装函数覆盖,需用 functools.wraps 保留:

import functools

def my_decorator(func):
    @functools.wraps(func)  # 保留原函数属性
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def say_hello(name):
    """这是一个打招呼的函数"""
    print(f"Hello, {name}")

print(say_hello.__name__)  # 输出 say_hello(不加 wraps 会输出 wrapper)

四、常见面试手写例子

1. 计时装饰器
import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 执行耗时:{end - start:.2f}秒")
        return result
    return wrapper

@timer
def test():
    time.sleep(1)
    print("test 函数执行完毕")

test()
2. 日志装饰器
import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用函数:{func.__name__},参数:args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 执行完毕,返回值:{result}")
        return result
    return wrapper

@log
def add(a, b):
    return a + b

add(1, 2)

五、进阶用法(加分项)

1. 带参数的装饰器

需三层函数:最外层接收装饰器参数,中间层接收原函数,最内层是包装函数:

import functools

def log_with_level(level):  # 第一层:接收装饰器参数
    def decorator(func):    # 第二层:接收原函数
        @functools.wraps(func)
        def wrapper(*args, **kwargs):  # 第三层:包装函数
            print(f"[{level}] 调用函数:{func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log_with_level(level="INFO")  # 传入参数
def say_hello(name):
    print(f"Hello, {name}")

say_hello("Tom")
2. 类装饰器

通过类的 __call__ 方法实现,可保持状态(如计数):

import functools

class Counter:
    def __init__(self, func):
        self.func = func
        self.count = 0  # 保持状态:调用次数

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} 已调用 {self.count} 次")
        return self.func(*args, **kwargs)

@Counter
def say_hello(name):
    print(f"Hello, {name}")

say_hello("Tom")
say_hello("Jerry")

总结

  • 装饰器是语法糖,本质是「高阶函数 + 闭包」;
  • 作用是不修改原代码扩展功能,符合开闭原则;
  • 需掌握 functools.wraps、带参数的装饰器、类装饰器等细节。

闭包和高阶函数

一、高阶函数(Higher-order Function)

1. 定义

满足以下任意一个条件的函数,就是高阶函数:

  • 接收一个或多个函数作为参数;
  • 返回值是一个函数
2. 核心依赖

Python 中「函数是一等公民」(First-class Citizen),即函数可以像变量一样:

  • 赋值给变量;
  • 作为参数传递;
  • 作为返回值返回。
3. 常见例子
例子 1:接收函数作为参数(如内置函数 mapfilter
# 自定义高阶函数:接收 func 作为参数
def apply_func(func, data):
    return [func(x) for x in data]

# 被传递的函数
def square(x):
    return x * x

# 使用
result = apply_func(square, [1, 2, 3])
print(result)  # 输出 [1, 4, 9]
例子 2:返回值是函数
def create_multiplier(n):
    # 定义内层函数
    def multiplier(x):
        return x * n
    # 返回内层函数
    return multiplier

# 使用:create_multiplier(2) 返回 multiplier 函数
double = create_multiplier(2)
print(double(5))  # 输出 10(x=5, n=2)

二、闭包(Closure)

1. 定义

闭包是一种特殊的函数,需同时满足三个条件:

  1. 存在内层函数嵌套在外层函数中;
  2. 内层函数引用了外层函数的变量(非全局变量);
  3. 外层函数返回了内层函数

此时,内层函数会「记住」外层函数的变量状态,即使外层函数执行完毕,内层函数仍能访问外层变量。

2. 经典例子:累加器
def make_accumulator():
    # 外层变量:sum_total
    sum_total = 0
    
    # 内层函数:引用外层变量 sum_total
    def accumulator(num):
        nonlocal sum_total  # 声明使用外层变量(Python3 需加,Python2 用可变对象如列表)
        sum_total += num
        return sum_total
    
    # 外层返回内层函数
    return accumulator

# 使用
acc = make_accumulator()
print(acc(1))  # 输出 1(sum_total=1)
print(acc(2))  # 输出 3(sum_total=1+2=3)
print(acc(3))  # 输出 6(sum_total=3+3=6)

关键点:make_accumulator 执行完毕后,sum_total 变量不会被销毁,而是被闭包 accumulator 「捕获」并保留状态。

三、高阶函数 vs 闭包 vs 装饰器的关系(核心考点)

  • 高阶函数是「函数操作函数」的基础;
  • 闭包是「保留外层变量状态」的特殊函数;
  • 装饰器 = 高阶函数 + 闭包(装饰器通过高阶函数接收原函数,通过闭包包装原函数并保留状态)。

总结

  • 高阶函数:接收函数为参数,或返回函数;
  • 闭包:内层函数引用外层变量,外层返回内层,可保留状态;
  • 两者结合是实现装饰器的核心原理。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐