Python高阶技巧(十二)

闭包(Closure)

闭包是一种非常重要的 Python 高阶函数特性,它能让内部函数“记住”外部函数的变量,即使外部函数已经执行完毕。

一句话总结:

闭包 = 函数嵌套 + 内部函数使用外部函数变量 + 外部函数返回内部函数。

闭包的核心能力是——
保存外部函数的变量,不会随着外部函数结束而销毁。


闭包的基本定义

闭包满足三个条件:

  1. 必须有函数嵌套(即函数内部再定义函数)
  2. 内部函数使用了外部函数的变量
  3. 外部函数返回内部函数

示例:

# 外部函数
def func_out(num1):
    # 内部函数
    def func_inner(num2):
        num = num1 + num2   # 使用了外部变量 num1
        print(f"num 的值为:{num}")

    return func_inner       # 返回内部函数形成闭包

# 创建闭包实例
f = func_out(10)
f(6)   # 打印:num 的值为:16

说明:

  • func_out 执行结束后,num1 按理应销毁
  • 但闭包保留了这个变量的值
  • 内部函数 func_inner 可以继续访问 num1

闭包中修改外部变量(nonlocal)

当内部函数想修改外部函数的变量时,必须使用 nonlocal 声明:

def func_out(num1):
    def func_inner(num2):
        nonlocal num1      # 声明使用外部函数变量
        num1 += num2
        print(f"num1 的值为:{num1}")

    return func_inner

f = func_out(10)
f(8)   # 打印:num1 的值为:18

nonlocal 的作用:

  • 表示 “这个变量不是本地变量,请向外层函数查找并绑定它”
  • 否则 Python 会认为 num1 是 func_inner 内的新变量,导致报错

闭包的优点与缺点

优点

  • 无需定义全局变量即可保存数据状态(例如计数器、缓存等场景)
  • 变量被封装在外部函数作用域内,更安全,不易被随意修改
  • 代码结构更清晰,易读性强

缺点

  • 闭包保存的外部变量 不会销毁
    → 有可能带来额外的 内存占用

小结

闭包的本质作用是:

让外部函数的变量在函数结束后依然存活,并由内部函数继续使用或修改。

它是 Python 函数式编程的重要基础,也为装饰器机制提供了前提条件。


装饰器(Decorator)

装饰器是 Python 中最强大的语法特性之一,它的作用是:

在不修改原函数代码的前提下,为其增加新的功能。

装饰器本质上依赖闭包(内部函数持有外部变量)。
它让代码变得更优雅、更灵活,尤其适用于:

  • 记录日志
  • 权限校验
  • 性能统计
  • 事务管理
  • 缓存机制
  • 统一接口格式

为什么需要装饰器?(动机)

假设有一个函数:

def func():
    print("这是一个函数")

现在想给它增加一个功能:执行前输出“开始执行”

传统做法是:

def func():
    print("开始执行")
    print("这是一个函数")

但这会修改原函数代码,且如果很多函数都需要增加相同逻辑,会产生大量重复代码。

装饰器能完美解决该问题。


装饰器的基本结构

装饰器依赖闭包机制:

def decorator(func):         # 接收被装饰的函数
    def wrapper(*args, **kwargs):
        print("开始执行")
        result = func(*args, **kwargs)  # 调用原函数
        print("结束执行")
        return result
    return wrapper

使用装饰器:

@decorator
def func():
    print("这是一个函数")

func()

输出:

开始执行
这是一个函数
结束执行

说明:

  • @decorator 等价于:func = decorator(func)
  • wrapper 是对原函数的“增强版本”
  • 不修改 func 本体,却达成增强效果

装饰器语法糖(@ 语法)

@decorator
def test():
    ...

等价于:

def test():
    ...
test = decorator(test)

@ 是增强函数最优雅的写法。


带参数的装饰器

有时希望装饰器可以“配置参数”,例如:

  • 配置日志等级
  • 设置权限等级
  • 设置统计模式

此时需要再多包一层函数:

def outer(flag):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print("装饰器参数:", flag)
            return func(*args, **kwargs)
        return wrapper
    return decorator

使用:

@outer("DEBUG")
def func():
    print("函数执行")

func()

输出:

装饰器参数: DEBUG
函数执行

原理:

  • outer(flag) 返回 decorator
  • decorator(func) 返回 wrapper

装饰器叠加三层函数 → 即可支持参数。


装饰器处理返回值

wrapper 必须返回原函数的返回值:

def decorator(func):
    def wrapper(*args, **kwargs):
        print("开始执行")
        result = func(*args, **kwargs)
        print("结束执行")
        return result
    return wrapper

多个装饰器叠加执行顺序

示例:

def deco1(func):
    def wrapper():
        print("deco1 start")
        func()
        print("deco1 end")
    return wrapper

def deco2(func):
    def wrapper():
        print("deco2 start")
        func()
        print("deco2 end")
    return wrapper

@deco1
@deco2
def func():
    print("函数执行")

执行:

func()

结果:

deco1 start
deco2 start
函数执行
deco2 end
deco1 end

规律:

  • 离函数最近的装饰器先执行
  • 装饰器从下往上包裹(类似洋葱结构)

保留原函数信息:functools.wraps

被装饰的函数会丢失原函数信息:

func.__name__  → wrapper

解决方法:

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

这样就能保持函数名、注释等元信息。


类装饰器(使用 call)

装饰器不仅可以是函数,还可以是类,只需实现 __call__ 方法:

class MyDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("类装饰器开始")
        result = self.func(*args, **kwargs)
        print("类装饰器结束")
        return result

使用:

@MyDecorator
def func():
    print("函数执行")

func()

输出:

类装饰器开始
函数执行
类装饰器结束

类装饰器的优点:

  • 更适合保存状态(如计数器、缓存)
  • 可扩展性强

装饰器的使用场景

装饰器是真正的工程化工具,常用于:

场景示例
日志记录@log_decorator
权限校验@auth_required
缓存@lru_cache
性能监控@timeit
事务控制@transactional
API 规范@response_format

装饰器让横切逻辑(Cross-Cutting Logic)更加优雅。


本章总结

内容描述
装饰器本质闭包函数增强原函数
语法糖@decorator
带参数装饰器三层函数结构
多装饰器从下往上包裹,从上往下执行
类装饰器使用 call 增强函数
wraps保留原函数元信息

一句话总结:

装饰器是一种无侵入式增强函数的技术,Python 中的“魔法工具”。


生成器(Generator)

生成器是 Python 中一种非常重要的特殊迭代器,它可以“边计算边返回数据”,不需要一次性把所有数据加载到内存中。

一句话总结:

生成器 = 带有 yield 的函数,可以逐个返回数据,实现惰性求值。

生成器在处理大量数据、长序列、流式数据输出时非常高效。


为什么需要生成器?

普通函数:

  • 一次性计算
  • 一次性返回
  • 占用大量内存

生成器:

  • 需要时才生成数据(惰性求值)
  • 不占用额外内存
  • 支持无限序列输出

实际应用场景:

  • 读取大文件
  • 网络流式数据
  • 无限序列(如斐波那契数列)
  • 节省内存的大规模数据处理

生成器的基本语法(yield)

使用 yield 替代 return

def func():
    print("执行1")
    yield 10

    print("执行2")
    yield 20

g = func()

使用:

print(next(g))   # 执行到第一个 yield
print(next(g))   # 执行到第二个 yield

运行结果:

执行1
10
执行2
20

说明:

  • 每次 next() 执行到下一个 yield
  • yield 返回值给调用者
  • yield 会记住函数状态,下次继续执行

使用生成器遍历数据

def generator():
    for i in range(3):
        print("生成:", i)
        yield i

for v in generator():
    print("接收:", v)

输出:

生成:0
接收:0
生成:1
接收:1
生成:2
接收:2

生成器非常适合“逐行处理文件”“逐条处理数据”等场景。


生成器表达式(更简洁的写法)

列表推导式:

lst = [x * 2 for x in range(5)]

生成器表达式:

gen = (x * 2 for x in range(5))

len(lst) = 5,立即创建所有数据
len(gen) 无法计算 → 数据按需生成

获取数据:

for i in gen:
    print(i)

send() 方法(向生成器发送数据)

生成器不仅可以产出数据,还可以接收数据。

示例:

def func():
    x = yield "hello"
    print("收到:", x)

g = func()
print(next(g))        # 启动生成器
g.send("Python")      # 发送数据

输出:

hello
收到: Python

说明:

  • 第一次必须用 next() 启动生成器
  • send(value) 会把 value 作为 yield 表达式的返回值

send 的典型用途:

  • 协程(早期协程机制)
  • 外部动态控制生成器内部逻辑

生成器实现无限序列

例如无限斐波那契:

def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

g = fib()

for _ in range(10):
    print(next(g))

生成器天然适合这种“无限值序列”,不会占用额外内存。


生成器的优点

优点描述
节省内存不需要一次性创建全部数据
惰性求值用到时才生成
易于表达无限序列无限循环 + yield
适用于大数据流处理如大文件读取、网络流

本章总结

特性说明
yield让函数成为生成器
next()获取下一个值
send()向生成器发送数据
生成器表达式(x for x in …)
惰性求值节省内存的核心

一句话总结:

生成器让 Python 更优雅、更高效,是高阶技巧中必掌握的核心能力之一。


迭代器(Iterator)

迭代器是 Python 中支持“逐个取值”的特殊对象,它是 for 循环背后的核心执行机制。

一句话总结:

迭代器 = 能被 next() 调用、能逐个返回数据的对象。

Python 中所有可迭代对象(如 list、dict、str、tuple)内部都依赖迭代器机制。


可迭代对象(Iterable)与迭代器(Iterator)

必须区分两个概念:

类型特点示例
Iterable(可迭代对象)能使用 for 循环遍历list、str、tuple、dict
Iterator(迭代器)能被 next() 调用,每次返回一个值文件对象、生成器、iter() 创建的对象

判断对象是否为可迭代对象:

from collections.abc import Iterable

print(isinstance([1,2,3], Iterable))  # True

判断是否为迭代器:

from collections.abc import Iterator

print(isinstance(iter([1,2,3]), Iterator))  # True

iter() 将可迭代对象转换成迭代器

例如:

lst = [10, 20, 30]
it = iter(lst)

逐个取值:

print(next(it))  # 10
print(next(it))  # 20
print(next(it))  # 30

如果继续 next(it) 会抛出 StopIteration,表示“没有更多数据”。

for 循环就是不断执行 next(),直到 StopIteration。


为什么需要迭代器?

迭代器相比普通序列有以下优势:

节省内存

数据不需要一次性加载(例如文件逐行读取)。

惰性求值

只有在需要时才取下一个元素。

统一遍历接口

for 循环可以遍历各种容器,因为它们都遵循迭代器协议。


自定义迭代器

要让一个类成为迭代器,必须实现两个方法:

  • __iter__():返回迭代器对象本身
  • __next__():返回下一个值,没有更多数据时抛出 StopIteration

示例:自定义迭代器,生成 1 到 n 的数字。

class MyIterator:
    def __init__(self, n):
        self.n = n
        self.current = 1

    def __iter__(self):
        return me

    def __next__(self):
        if self.current <= self.n:
            value = self.current
            self.current += 1
            return value
        else:
            raise StopIteration

使用:

for i in MyIterator(5):
    print(i)

输出:

1
2
3
4
5

自定义可迭代对象(Iterable)

如果你想让一个对象可用于 for 遍历,需要:

  • 实现 __iter__() 方法
  • 返回一个迭代器对象(如上面的 MyIterator)

示例:

class MyList:
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        return iter(self.data)  # 返回系统提供的迭代器

使用:

ml = MyList([1, 2, 3])
for x in ml:
    print(x)

说明:

  • MyList 本身不是迭代器,但它是可迭代对象
  • for 循环内部会自动调用 iter(ml),从而获取迭代器

生成器就是一种迭代器

生成器(含 yield 的函数)天然拥有 iternext 方法,是迭代器的一种。

验证:

def gen():
    yield 1
    yield 2

g = gen()

from collections.abc import Iterator
print(isinstance(g, Iterator))   # True

StopIteration 的作用

迭代器必须通过 StopIteration 来告诉循环:

“已经没有更多数据了。”

for 循环内部结构如下(概念化):

it = iter(obj)
while True:
    try:
        value = next(it)
        # 使用 value
    except StopIteration:
        break

这就是 for 循环不需要索引的原因。


本章总结

概念说明
Iterable能被 for 遍历
Iterator能执行 next()
iter()把可迭代对象变成迭代器
next()获取下一个值
StopIteration表示迭代结束
自定义迭代器实现 iternext
生成器天然迭代器

一句话总结:

迭代器是 Python 遍历机制的核心,for 的底层就是不断调用 next()。


Lambda 表达式与内置高阶函数

本章介绍 Python 中最常用的高阶函数与匿名函数技巧,包括:

  • Lambda 匿名函数
  • map
  • filter
  • sorted
  • reduce

它们是编写简洁、高效 Python 代码的必备工具。


Lambda 匿名函数

Lambda 是一种 匿名函数,用于快速声明简单函数。

基本语法:

lambda 参数: 表达式

示例:

func = lambda x: x * 2
print(func(5))   # 10

等价于:

def func(x):
    return x * 2

适用场景:

  • 函数非常简单
  • 临时使用一次
  • 与高阶函数搭配(map / filter / sorted)

map(对每个元素执行函数)

语法:

map(函数, 可迭代对象)

示例:所有数字乘 2

nums = [1, 2, 3, 4]
result = map(lambda x: x * 2, nums)
print(list(result))

输出:

[2, 4, 6, 8]

作用:

map = 批量加工工具,将每个元素做变换


filter(过滤元素)

语法:

filter(函数, 可迭代对象)

函数返回 True 的元素会被保留下来。

示例:筛选偶数

nums = [1, 2, 3, 4]
result = filter(lambda x: x % 2 == 0, nums)
print(list(result))

输出:

[2, 4]

作用:

filter = 批量筛选工具


sorted(排序,支持 key 规则)

基本排序:

sorted([3, 1, 2])   # [1, 2, 3]

按 key 排序(更常用):

students = [
    {"name": "Tom", "age": 19},
    {"name": "Lucy", "age": 17},
    {"name": "Jack", "age": 20},
]

result = sorted(students, key=lambda x: x["age"])

输出:

[{'name': 'Lucy', 'age': 17}, {'name': 'Tom', 'age': 19}, {'name': 'Jack', 'age': 20}]

倒序:

sorted(students, key=lambda x: x["age"], reverse=True)

作用:

sorted + key = 自定义排序规则


reduce(连续处理数据)

reduce 用于对序列进行“累积处理”。

需要导入:

from functools import reduce

示例:所有数相加

nums = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, nums)
print(result)

输出:

10

reduce 的执行过程:

(((1 + 2) + 3) + 4)

常用于:

  • 求和
  • 连乘
  • 合并结构
  • 递归处理逻辑

高阶函数综合示例

任务:筛选出大于 10 的数,然后平方,并按结果排序。

nums = [3, 15, 2, 20, 9]

# 1. 过滤
filtered = filter(lambda x: x > 10, nums)

# 2. 映射(平方)
mapped = map(lambda x: x * x, filtered)

# 3. 排序
result = sorted(mapped)

print(result)

输出:

[225, 400]

一条语句也能写:

result = sorted(map(lambda x: x * x, filter(lambda x: x > 10, nums)))

这就是函数式写法的威力。


本章小结

技术作用
lambda快速定义简单函数
map批量加工元素
filter批量筛选元素
sorted自定义排序规则
reduce连续运算、累积处理

一句话总结:

Lambda + 高阶函数 = Python 中编写简洁优美代码的高级技巧。


推导式(Comprehensions)

推导式是 Python 中用于构建列表、字典、集合的简洁写法,具有:

  • 更少的代码量
  • 更高的可读性
  • 更快的执行效率

一句话总结:

推导式 = 用一个表达式构造一个新容器

它是 Python 高阶技巧中最常用也最实用的语法糖。


列表推导式(List Comprehension)

基本结构:

[表达式 for 变量 in 可迭代对象]

示例:

lst = [x * 2 for x in range(5)]
print(lst)

输出:

[0, 2, 4, 6, 8]

带条件筛选

lst = [x for x in range(10) if x % 2 == 0]

输出:

[0, 2, 4, 6, 8]

复杂表达式示例

names = ["Tom", "Jack", "Linda"]
result = [name.upper() for name in names]

输出:

['TOM', 'JACK', 'LINDA']

字典推导式(Dict Comprehension)

基本结构:

{key_expr: value_expr for 变量 in 可迭代对象}

示例:

nums = [1, 2, 3]
d = {x: x * x for x in nums}
print(d)

输出:

{1: 1, 2: 4, 3: 9}

带条件的字典推导式

d = {x: x * 2 for x in range(6) if x % 2 == 0}

输出:

{0: 0, 2: 4, 4: 8}

字典反转(常用)

origin = {"Tom": 18, "Jack": 20}
reversed_dict = {v: k for k, v in origin.items()}

输出:

{18: 'Tom', 20: 'Jack'}

集合推导式(Set Comprehension)

基本结构:

{表达式 for 变量 in 可迭代对象}

示例:

s = {x * 2 for x in range(5)}
print(s)

输出(集合无序):

{0, 2, 4, 6, 8}

去重场景(非常常用)

例如对一组数据取平方并去重:

s = {x * x for x in [1, 2, 2, 3, 3]}
print(s)

输出:

{1, 4, 9}

嵌套推导式(高级使用)

生成九九乘法表:

table = [
    f"{i}*{j}={i*j}"
    for i in range(1, 10)
    for j in range(1, i + 1)
]

输出示例:

['1*1=1', '2*1=2', '2*2=4', ...]

或生成二维列表:

matrix = [[i + j for j in range(3)] for i in range(3)]

输出:

[[0, 1, 2], [1, 2, 3], [2, 3, 4]]

推导式与函数式编程对比

例如:筛选偶数并平方

使用 map + filter:

result = list(map(lambda x: x * x, filter(lambda x: x % 2 == 0, range(10))))

使用推导式(更直观):

result = [x * x for x in range(10) if x % 2 == 0]

→ 推导式更加简洁、可读性更强。


推导式的优势

优势说明
简洁一行解决多行逻辑
高效比普通 for 循环速度更快
可读性强结构统一、逻辑清晰
灵活支持筛选、表达式、嵌套等

本章小结

推导式类型示例
列表推导式[x*2 for x in data]
字典推导式{k:v for k,v in items}
集合推导式{x for x in data}
带条件推导式[x for x in data if x>0]
嵌套推导式[[... for j] for i]

一句话总结:

推导式是 Python 编程优雅性的象征,让复杂处理逻辑变得异常简洁。


异常高级技巧

基础异常处理你已经很熟悉:try / except / finally
本章将介绍更专业、更工程化的异常技巧,包括:

  • 多异常捕获
  • 捕获所有异常
  • 自定义异常
  • 异常链(raise from)
  • 异常安全代码结构
  • 使用异常控制业务流程

这些技巧在真实项目中非常实用。


多异常捕获

当你需要同时处理多种不同类型的异常时,可以写多个 except:

try:
    num = int(input("输入数字:"))
    print(10 / num)

except ValueError:
    print("输入必须是数字")

except ZeroDivisionError:
    print("不能除以 0")

适用于不同异常需要不同处理逻辑的情况。


合并捕获多个异常

如果多个异常处理逻辑一致:

try:
    num = int("abc")
except (ValueError, TypeError):
    print("数据格式错误")

用括号统一捕获,有助于代码简化。


捕获所有异常(Exception)

try:
    ...
except Exception as e:
    print("错误信息:", e)

适用于:

  • 复杂逻辑
  • 无法预估具体异常类型
  • 提升系统稳定性

不建议滥用,否则真实错误会被掩盖。


自定义异常

用于业务逻辑中出现“非系统报错,但需要使用异常机制表示错误”的情况。

定义异常需要继承 Exception:

class PasswordTooShortError(Exception):
    pass

使用:

def set_password(pwd):
    if len(pwd) < 6:
        raise PasswordTooShortError("密码长度不能少于 6 位")

捕获:

try:
    set_password("123")
except PasswordTooShortError as e:
    print("密码错误:", e)

自定义异常非常适合:

  • 业务错误提示
  • 表示特殊流程跳转
  • 框架设计(如 Django、Flask)

异常链(raise from)

当你想在捕获一个异常后,抛出另一个新的异常,同时保留原始异常信息:

try:
    int("abc")
except ValueError as e:
    raise RuntimeError("转换失败") from e

输出会显示:

During handling of the above exception, another exception occurred:

→ 非常有利于调试!


finally 的作用(无论如何都会执行)

finally 常用于:

  • 关闭文件、数据库连接
  • 释放资源
  • 打印日志

示例:

try:
    f = open("a.txt")
    data = f.read()

except Exception:
    print("文件读取失败")

finally:
    f.close()

即使发生异常,也会执行 close()


else:没有异常时执行

很多人不知道 else 是 try-except 的一部分:

try:
    num = int("123")
except:
    print("出错")
else:
    print("成功执行,没有异常")

适用于:

  • 想区分“正常逻辑”和“异常处理”
  • 让代码结构更清晰

使用异常进行业务控制(高阶用法)

示例:检测年龄是否合法

class AgeError(Exception):
    pass

def set_age(age):
    if age < 0:
        raise AgeError("年龄不能为负数")
    print("年龄设置成功")

说明:

  • 比 if-else 更强的流程控制
  • 错误直接中断当前流程
  • 在大型业务系统中常见

记录异常日志(企业级写法)

import traceback

try:
    1 / 0
except Exception as e:
    with open("error.log", "a") as f:
        f.write(traceback.format_exc())

优势:

  • 保留完整错误堆栈
  • 易于排查线上问题

本章总结

技巧说明
多异常捕获为不同错误设计不同逻辑
Exception 捕获捕获所有异常
自定义异常用于业务逻辑错误处理
raise from构建异常链,利于调试
finally无论是否异常都执行
else仅在无异常时执行
异常日志记录保存错误堆栈,定位问题

一句话总结:

高级异常处理的目标不是“捕获更多异常”,而是让系统更稳定、可控、易调试。


上下文管理器(with 语句)

上下文管理器是 Python 中用于自动管理资源的机制,最常见的例子就是:

with open("a.txt") as f:
    ...

with 的核心优势:

  • 自动执行“进入 → 离开”逻辑
  • 自动关闭文件、连接等资源
  • 自动处理异常场景确保资源释放

一句话总结:

上下文管理器 = 自动执行善后工作的机制。


为什么需要上下文管理器?

不使用 with 时,你必须手动关闭资源:

f = open("a.txt", "r")
try:
    data = f.read()
finally:
    f.close()

使用 with:

with open("a.txt", "r") as f:
    data = f.read()

自动关闭资源,更优雅、更安全。


上下文管理器的原理:enter 与 exit

任意对象只要实现了下面两个方法,就能成为一个上下文管理器:

__enter__(self)
__exit__(self, exc_type, exc_val, exc_tb)

流程如下:

  1. 执行 with 时,自动调用 __enter__()
  2. 执行代码块
  3. 无论是否产生异常,最终都会调用 __exit__()

自定义上下文管理器(类方式)

示例:自定义一个能自动打印执行流程的上下文管理器。

class MyContext:
    def __enter__(self):
        print("进入上下文")
        return "返回给 as 的值"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("退出上下文")
        if exc_type:
            print("发生异常:", exc_val)
        return True   # 防止异常继续抛出

使用:

with MyContext() as msg:
    print(msg)

输出:

进入上下文
返回给 as 的值
退出上下文

上下文管理器处理异常

如果 with 内部代码报错:

with MyContext():
    raise ValueError("测试异常")

exit 的三个参数:

  • exc_type:异常类型
  • exc_val:异常对象
  • exc_tb:异常的 traceback

如果 exit 返回:

  • True:异常会被吞掉(不继续抛出)
  • False:异常会继续抛出

使用 contextlib.contextmanager(装饰器写法)

相比类写法,函数写法更轻量。
依赖装饰器:

from contextlib import contextmanager

示例:

@contextmanager
def my_context():
    print("进入上下文")
    yield "hello"
    print("退出上下文")

使用:

with my_context() as msg:
    print(msg)

输出:

进入上下文
hello
退出上下文

说明:

  • yield 前的代码类似 enter
  • yield 后的代码类似 exit
  • 写法更简洁优雅

应用场景

上下文管理器常用于管理需要释放的资源:

场景示例
文件操作open()
数据库连接with connection.cursor():
线程锁with lock:
网络连接自动关闭 socket
事务控制自动提交 / 回滚
临时目录、测试环境with TemporaryDirectory()

在大型 Python 项目中,with 是代码质量的重要标志。


本章总结

技巧说明
with 语句自动管理资源
enter上下文进入逻辑
exit上下文退出逻辑(含异常处理)
contextmanager使用装饰器定义上下文管理器
应用场景文件、数据库、锁、事务等

一句话总结:

上下文管理器让资源使用更安全,让代码更优雅,是 Python 工程化必备技巧。


模块导入高级技巧

Python 的模块导入系统非常灵活,本章将讲解:

  • __all__ 导出控制
  • 绝对导入与相对导入
  • 动态导入(importlib)
  • 模块导入本质
  • import 的执行机制

这些技巧在实际开发、模块化设计、项目重构中非常重要。


使用 __all__ 控制模块可导出内容

当你使用:

from module import *

时,Python 会根据模块内的 __all__ 列表决定能导出的内容。

示例:

__all__ = ["func1", "Student"]

def func1():
    print("函数1")

def func2():
    print("函数2")

class Student:
    pass

当执行:

from module import *

可用:

  • func1
  • Student

不可用:

  • func2(因为不在 all 中)

作用:

  • 明确模块对外暴露的接口
  • 保护内部函数不被直接使用
  • 让模块的 API 更清晰规范

绝对导入与相对导入

绝对导入(推荐)

基于项目根目录的导入方式:

from package.module import func

优点:

  • 清晰
  • 不易出错
  • 适合大型项目

相对导入

使用 . 和 .. 表示当前及上级目录:

from .module_a import func
from ..common.utils import helper

相对导入只能在包内使用。

标记含义:

写法含义
.当前目录
..上一级目录
...上上级目录

适用场景:

  • 包内部模块结构稳定
  • 需要避免重复书写长路径

动态导入(importlib)

在某些场景,如插件系统、按需加载模块时,需要动态导入模块。

import importlib

module = importlib.import_module("math")
print(module.sqrt(9))

输出:

3.0

还能按字符串导入自定义模块:

m = importlib.import_module("mypackage.mymodule")

常用于:

  • 插件系统
  • 动态配置加载
  • 根据字符串执行特定模块

模块只会被导入一次(import 缓存机制)

即使写了多次 import,相同模块只会执行一次。

示例:

import mymodule
import mymodule

mymodule.py 里的顶级代码只会执行一次。

原因:

  • Python 会将导入过的模块缓存到 sys.modules
  • 再次导入时会直接读取缓存

查看缓存:

import sys
print(sys.modules)

import 执行流程(理解模块导入本质)

当你执行:

import module

Python 实际做了三件事:

  1. 在 sys.path 中查找 module.py 或包目录
  2. 加载模块内容并执行其顶级代码
  3. 将模块对象放入 sys.modules 缓存

因此:

  • 任何写在模块最外层的代码都会执行一次
  • 函数与类只是被创建,不会自动执行

循环导入问题(Import Cycle)

例如:

  • a.py 导入 b.py
  • b.py 又导入 a.py

若存在互相依赖,就会导致:

ImportError: cannot import name ...

解决方案:

  1. 将公共逻辑抽离到独立模块
  2. 使用局部导入(在函数内部 import)
  3. 使用动态导入(importlib.import_module)

避免循环导入是模块化设计的关键。


局部导入(函数内部 import)

在函数内部导入模块:

def func():
    import math
    print(math.sqrt(4))

用途:

  • 避免循环依赖
  • 减少启动时间(按需加载)
  • 降低全局命名污染

缺点:

  • 多次调用时会重复查找,但缓存机制仍能减少开销

本章总结

技巧作用
__all__控制对外暴露的接口
相对导入包内部模块组织
动态导入插件机制、按需加载
import 缓存模块只执行一次
局部导入避免循环依赖
sys.path决定模块的查找顺序

一句话总结:

模块导入是 Python 工程化的核心能力,高级技巧让你的项目更规范、更灵活、更易维护。


Python 内存管理与变量机制

本章涵盖 Python 中最核心的底层机制之一,包括:

  • 引用计数
  • 垃圾回收机制
  • 可变与不可变对象
  • 深拷贝与浅拷贝
  • 变量名与对象的关系(非常关键)

理解这些知识能让你更清楚 Python 程序的运行方式,有助于避免内存泄漏、变量污染、意外修改等问题。


变量名不是“盒子”,而是“标签”

Python 中的变量本质上是对象的引用(指针),不是传统语言中的存储空间。

示例:

a = 10
b = a

含义不是“把 a 的值复制给 b”,而是:

a → 指向 10
b → 指向 10

a 和 b 都指向同一个对象。

如果你再执行:

a = 20

此时:

a → 指向 20(新的对象)
b → 仍然指向 10

变量只是名字,真正的数据存储在对象中


引用计数(Reference Counting)

Python 最主要的垃圾回收机制是引用计数:

每个对象都有一个引用计数器,当计数为 0 时,该对象会立即被销毁。

示例:

a = [1, 2, 3]
b = a
c = a

引用次数 = 3
若执行:

del b
del c

引用次数 = 1
再执行:

del a

引用次数 = 0 → 内存释放。

你可以使用 sys.getrefcount 查看引用计数:

import sys
x = 100
print(sys.getrefcount(x))

可变对象与不可变对象

Python 中的对象可分为:

不可变对象(修改会创建新对象)

  • int
  • float
  • str
  • tuple
  • bool

示例:

s = "abc"
s2 = s
s = "abcd"

“abcd” 是新对象,与原字符串 “abc” 无关。


可变对象(修改不会创建新对象)

  • list
  • dict
  • set

示例:

lst = [1, 2, 3]
lst2 = lst
lst.append(4)

结果:

lst → [1, 2, 3, 4]
lst2 → [1, 2, 3, 4]

原因:lst 与 lst2 指向同一个列表对象。


浅拷贝与深拷贝

浅拷贝(copy)

只复制最外层对象,内部引用保持不变。

import copy
lst = [[1, 2], [3, 4]]
lst2 = copy.copy(lst)

lst2[0][0] = 999
print(lst)   # [[999, 2], [3, 4]]

→ 内部列表被共用。


深拷贝(deepcopy)

完全复制整个结构,内部对象也会复制。

lst3 = copy.deepcopy(lst)
lst3[0][0] = 111
print(lst)   # 不受影响

适用场景:

  • 多层嵌套结构(如 JSON)
  • 复杂对象传递时避免互相影响

垃圾回收(GC)机制

Python 采用:

  1. 引用计数(主机制)
  2. 循环垃圾检测(GC)
  3. 分代回收

循环引用示例:

a = []
b = []
a.append(b)
b.append(a)

此时引用计数永远 > 0,但对象失去了实际用途。
→ Python 的循环垃圾回收器会自动回收这些“不可达对象”。


变量作用域(LEGB 原则)

LEGB 是 Python 查找变量名时的顺序:

L:Local 局部作用域
E:Enclosing 外层函数作用域
G:Global 全局
B:Built-in 内置作用域

示例:

x = 1

def outer():
    x = 2
    def inner():
        x = 3
        print(x)  # 输出 3
    inner()
outer()

Python 会从内到外一层一层查找变量。


global 与 nonlocal

global:修改全局变量

count = 0

def add():
    global count
    count += 1

global 作用:绑定外部(全局)变量。


nonlocal:修改外层函数变量(闭包)

def outer():
    x = 10
    def inner():
        nonlocal x
        x += 1
    inner()
    print(x)

输出:

11

nonlocal 用于闭包,是闭包修改外部变量的唯一方式。


本章总结

机制说明
引用计数对象计数归零 → 自动销毁
垃圾回收自动检测循环引用并清理
可变/不可变对象影响变量修改是否影响其他引用
浅拷贝外层复制,内层共享
深拷贝完全复制,不共享
global修改全局变量
nonlocal修改外层函数变量
变量名本质变量名 = 对象引用(标签)

一句话总结:

理解 Python 内存机制,可以避免意外数据共享、内存泄漏、变量污染等常见错误,是掌握 Python 高阶写法的关键。


元编程(Metaprogramming)

元编程指的是:代码可以操作代码本身
Python 作为动态语言,提供丰富的元编程能力,包括:

  • eval()
  • exec()
  • 反射(getattrsetattrhasattr
  • 动态创建变量
  • 动态执行函数
  • 动态导入模块

元编程让 Python 在动态场景下具有极强的灵活性,例如:

  • 根据字符串名字执行函数
  • 动态调用对象属性
  • 动态生成代码
  • 构建插件式系统

eval:执行字符串表达式并返回结果

作用:

把字符串当成 Python 表达式执行,并返回结果。

示例:

result = eval("1 + 2 + 3")
print(result)

输出:

6

eval 可执行任意表达式:

eval("len([1, 2, 3])")  # 3

⚠ 安全警告:
eval 会执行任意代码,不要在用户可控输入中使用。


exec:执行任意 Python 代码(不返回值)

exec 用于执行多行代码或复杂逻辑。

code = """
for i in range(3):
    print(i)
"""

exec(code)

输出:

0
1
2

区别:

函数作用
eval执行表达式并返回结果(单行)
exec执行代码块,不返回结果(多行)

反射(getattr / setattr / hasattr)

反射是 Python 动态语言最强大的能力之一:

根据“字符串名字”操作对象的属性或方法。

示例类:

class Student:
    def __init__(self):
        self.name = "Tom"

    def speak(self):
        print("hello")

getattr:根据字符串获取属性或方法

stu = Student()

attr = getattr(stu, "name")
print(attr)

输出:

Tom

获取方法:

method = getattr(stu, "speak")
method()   # 调用方法

setattr:动态设置属性

setattr(stu, "age", 20)
print(stu.age)

输出:

20

动态修改已有属性:

setattr(stu, "name", "Jack")

hasattr:判断属性是否存在

print(hasattr(stu, "name"))  # True
print(hasattr(stu, "age"))   # False(如果之前没设置)

反射常用于:

  • 动态调用方法(插件系统)
  • 框架开发(Django ORM、Flask 路由)
  • 根据配置调用类方法

globals() / locals()(动态变量操作)

globals():操作全局变量字典

globals()["x"] = 100
print(x)  # 100

动态创建变量名。

locals():操作局部变量(只读)

在函数中使用:

def func():
    print(locals())

func()

动态执行函数名(字符串 → 函数调用)

假设你有如下函数:

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

想根据字符串执行:

func_name = "add"
func = globals()[func_name]
print(func(3, 5))

输出:

8

这在:

  • 命令解析器
  • API 调用分发
  • 插件系统

中非常常用。


动态创建类实例

示例:

class Dog: pass
class Cat: pass

class_name = "Dog"
cls = globals()[class_name]
obj = cls()
print(obj)

可以根据字符串动态创建对象,非常灵活。


importlib:动态导入模块(插件系统核心)

import importlib

module = importlib.import_module("math")
print(module.sqrt(16))

可根据配置文件动态加载模块,实现插件化架构。


元编程的典型使用场景

场景示例
动态调用函数getattr(obj, “func_name”)()
插件机制importlib 动态导入模块
ORM 框架根据字段动态构建 SQL
自动路由匹配Flask 中根据 URL 字符串匹配函数
序列化根据属性名自动生成 JSON
自动构建对象配置驱动结构

元编程是 Python 在框架开发领域(Django、Flask、FastAPI)如此强大的核心原因。


本章总结

技术说明
eval执行表达式并返回结果
exec执行代码块,无返回值
getattr字符串 → 获取属性或方法
setattr字符串 → 设置属性
hasattr判断属性是否存在
globals动态全局变量操作
importlib动态导入模块
反射动态语言的灵魂能力

一句话总结:

元编程让 Python 拥有操作自身结构的能力,是高级开发(插件系统、框架开发、自动化系统)不可或缺的一部分。


综合案例:装饰器 + 闭包 + 迭代器 + 反射的高级应用

本综合案例展示如何将本章节中学习的高阶技巧组合在一起,包括:

  • 闭包
  • 装饰器
  • 迭代器
  • 反射
  • Lambda
  • 上下文管理器思想(资源管理)

这是一个典型的“高级 Python 项目技巧整合案例”。


需求背景

我们要实现一个“带日志记录功能的任务执行器”,要求:

  1. 任务可以动态注册(使用反射)
  2. 每个任务执行前后自动打印日志(装饰器)
  3. 任务执行器应能逐个输出任务(迭代器)
  4. 可以为某些任务保存状态(闭包)

最终展示 Python 高阶语法组合使用的完整能力。


步骤拆解

下面将分步骤构建案例。


使用闭包保存任务执行次数

闭包让函数拥有“记忆”。

def task_counter(task_name):
    count = 0

    def wrapper():
        nonlocal count
        count += 1
        print(f"任务 {task_name} 已执行 {count} 次")
    return wrapper

示例:

count_task = task_counter("download")
count_task()
count_task()

输出:

任务 download 已执行 1 次
任务 download 已执行 2 次

使用装饰器自动记录任务执行日志

def log_task(func):
    def wrapper(*args, **kwargs):
        print(f"[开始执行] {func.__name__}")
        result = func(*args, **kwargs)
        print(f"[执行完毕] {func.__name__}")
        return result
    return wrapper

用于为任意任务函数添加前后日志。


定义可动态调用的任务(反射机制)

class TaskCenter:

    @log_task
    def download(self):
        print("正在下载文件...")

    @log_task
    def upload(self):
        print("正在上传文件...")

    @log_task
    def clean(self):
        print("正在清理文件...")

现在任务中心拥有 3 个可执行任务。

反射可以让我们根据任务名字自动找到对应函数并执行:

task_center = TaskCenter()

task_name = "download"
getattr(task_center, task_name)()

用迭代器实现“任务队列”

任务队列类:

class TaskQueue:
    def __init__(self, tasks):
        self.tasks = tasks
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.tasks):
            task = self.tasks[self.index]
            self.index += 1
            return task
        else:
            raise StopIteration

使用:

queue = TaskQueue(["download", "upload", "clean"])
for task in queue:
    print("任务:", task)

综合任务执行器:所有能力整合

class TaskExecutor:
    def __init__(self, task_center):
        self.task_center = task_center

    def run(self, tasks):
        for task_name in tasks:
            if hasattr(self.task_center, task_name):
                func = getattr(self.task_center, task_name)
                func()  # 自动带日志(因为被装饰器增强)
            else:
                print(f"未知任务:{task_name}")

最终运行代码(可直接复制)

# 任务中心(带装饰器)
class TaskCenter:
    @log_task
    def download(self):
        print("正在下载文件...")

    @log_task
    def upload(self):
        print("正在上传文件...")

    @log_task
    def clean(self):
        print("正在清理文件...")


# 任务队列
queue = TaskQueue(["download", "upload", "clean"])

# 执行器
executor = TaskExecutor(TaskCenter())

executor.run(queue)

运行结果示例:

[开始执行] download
正在下载文件...
[执行完毕] download

[开始执行] upload
正在上传文件...
[执行完毕] upload

[开始执行] clean
正在清理文件...
[执行完毕] clean

本章总结

本综合案例整合了本 PDF 中所有关键高阶技巧:

技巧用法
闭包保存状态(执行次数)
装饰器自动添加日志功能
迭代器实现任务队列
反射根据字符串动态执行任务
动态函数灵活扩展任务系统
高阶编程思想开放可扩展的结构设计

一句话总结:

本案例展示了 Python 高阶技巧如何组合成强大的工程化能力,体现了 Python 动态语言的真正威力。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇