找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3834

积分

0

好友

532

主题
发表于 4 小时前 | 查看: 4| 回复: 0

欢迎来到 Python 进阶学习的第四天!今天,我们将深入探讨两个能让你的代码变得更加优雅、安全且易于复用的高级特性:装饰器 (Decorator)上下文管理器 (Context Manager)

简单来说:

  • 装饰器 → 让你无需修改原函数代码,就能为其“穿”上新的功能外衣,如日志、计时等。
  • 上下文管理器 → 确保像文件、数据库连接这样的资源,其“获取、使用、释放”的整个生命周期都被妥善管理。

核心概念速览

技术 作用 典型场景
函数装饰器 在不修改原函数代码的前提下,增加功能 日志、计时、权限校验
类装饰器 用类实现装饰逻辑(适合有状态的装饰) 缓存、重试计数
上下文管理器 确保资源“获取→使用→释放”完整生命周期 文件操作、数据库事务

实践一:编写一个实用的计时装饰器

目标:测量任意函数的执行耗时。

方式一:基础函数装饰器

# decorators.py
import time
from functools import wraps

def timer(func):
    """计时装饰器:打印函数执行时间"""
    @wraps(func)  # 保留原函数的元信息(如 __name__, __doc__)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)  # 执行原函数
        end = time.time()
        print(f"⏱️  {func.__name__} 耗时: {end - start:.4f} 秒")
        return result
    return wrapper

# 使用示例
@timer
def slow_function():
    time.sleep(1)
    return "完成"

print(slow_function())  # 输出: ⏱️ slow_function 耗时: 1.0042 秒

关键点

  • @wraps(func) 用于保留原函数的名称和文档字符串,否则函数名会变成 wrapper
  • *args, **kwargs 让装饰器能够接受任意数量和类型参数,使其更具通用性。

方式二:带参数的装饰器(进阶)

有时候,我们想为装饰器传入一些配置,比如给计时器加个标签。

def timer_with_label(label="执行"):
    """可自定义标签的计时装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print(f"⏱️  [{label}] {func.__name__}: {end - start:.4f} 秒")
            return result
        return wrapper
    return decorator

# 使用
@timer_with_label("数据处理")
def process_data():
    time.sleep(0.5)

process_data()  # 输出: ⏱️ [数据处理] process_data: 0.5021 秒

实践二:了解类装饰器

当装饰逻辑需要保存状态(例如记录函数被调用了多少次)时,使用类来实现会更清晰。

class CountCalls:
    """类装饰器:统计函数调用次数"""
    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)

@CountCalls
def say_hello():
    print("Hello!")

say_hello()  # 📞 say_hello 已被调用 1 次
say_hello()  # 📞 say_hello 已被调用 2 次

适用场景:需要保存状态(如计数、缓存)的装饰逻辑。

实践三:自定义文件读取上下文管理器

危险写法(资源可能未释放)
如果 open()close() 之间发生异常,文件可能无法正常关闭,导致资源泄露。

def read_file_bad(filename):
    f = open(filename)
    data = f.read()
    # 如果这里发生异常,f.close() 不会执行!
    f.close()
    return data

安全写法一:使用内置 with open
这是最推荐的方式,简洁且安全。

def read_file_good(filename):
    with open(filename) as f:
        return f.read()
    # 自动调用 f.close()

安全写法二:自定义上下文管理器类
为了深入理解原理,我们可以自己实现一个。

# context_managers.py
class SafeFileReader:
    """自定义文件读取上下文管理器"""
    def __init__(self, filename, mode='r', encoding="utf-8"):
        if mode not in ('r', 'w', 'a', 'r+', 'w+', 'a+'):
            raise ValueError(f"不支持的文件模式: {mode}")
        self.filename = filename
        self.mode = mode
        self.encoding = encoding
        self.file = None

    def __enter__(self):
        """进入 with 块时调用,打开文件"""
        print(f"📂 打开文件: {self.filename}")
        self.file = open(self.filename, mode=self.mode, encoding=self.encoding)
        return self.file  # 返回给 as 变量

    def __exit__(self, exc_type, exc_val, exc_tb):
        """退出 with 块时调用(无论是否发生异常)"""
        if self.file:
            self.file.close()
            print(f"🗑️  关闭文件: {self.filename}")
        # 返回 False 表示不抑制异常(默认行为)
        return False

# 使用
try:
    with SafeFileReader("test.txt") as f:
        content = f.read()
        print("内容:", content[:50])
except FileNotFoundError:
    print("❌ 文件未找到")

__exit__ 参数说明

  • exc_type:异常类型(如 FileNotFoundError
  • exc_val:异常实例
  • exc_tb:traceback 对象
  • 返回 True 可抑制异常(慎用!)

实战应用:结合银行系统场景

场景一:用装饰器记录关键操作耗时
在银行系统中,监控重要交易操作的耗时是常见需求,使用装饰器可以无侵入地实现。

# bank.py
from decorators import timer

class BankAccount:
    # ...
    @timer
    def deposit(self, amount):
        # 模拟复杂的业务校验(实际可能涉及风控、日志等)
        time.sleep(0.01)
        self._balance += amount
        return True

场景二:用上下文管理器安全加载账户数据
从文件或数据库安全地加载账户信息,确保连接或文件句柄能被正确关闭。

# bank.py
from context_managers import SafeFileReader
import json

def load_accounts():
    try:
        with SafeFileReader("data/accounts.jsonl") as f:
            for line in f:
                yield json.loads(line.strip())
    except FileNotFoundError:
        logger.warning("账户文件不存在,返回空")
        return

完整测试代码

# main.py
from decorators import timer, timer_with_label
from context_managers import SafeFileReader

# 测试装饰器
@timer
def calculate_sum(n):
    return sum(range(n))

result = calculate_sum(1000000)  # ⏱️ calculate_sum 耗时: 0.0421 秒

# 测试上下文管理器
with SafeFileReader("example.txt", "w") as f:
    f.write("Hello, Context Manager!")

with SafeFileReader("example.txt") as f:
    print("读取内容:", f.read())  # 输出: 📂 打开... Hello, Context Manager! 🗑️ 关闭...

关键知识点总结

  1. 装饰器本质
    @decorator 只是语法糖,它的本质是函数调用。

    @timer
    def foo():
        pass
    # 等价于
    foo = timer(foo)
  2. 上下文管理器协议

    • 必须实现 __enter____exit__ 两个方法。
    • with obj as x 语句等价于 x = obj.__enter__()
    • 退出 with 块时,会自动调用 obj.__exit__(),即使中间发生了异常。
  3. 何时使用?

需求 推荐方案
添加日志、计时、缓存等通用功能 函数装饰器
装饰逻辑需要保存状态(如调用次数、缓存数据) 类装饰器
管理需要精确控制生命周期的资源(文件、锁、网络连接) 上下文管理器

小结:迈向优雅与安全

掌握装饰器和上下文管理器,意味着你的 Python 代码在朝着专业和健壮的方向迈进。

  • 装饰器 让功能增强像“穿衣服”一样简单,遵循了 DRY(Don‘t Repeat Yourself)原则。
  • 上下文管理器 让资源管理像“呼吸”一样自然,有效防止了资源泄露。

这两项技术是 Python 函数式编程和资源管理范式的核心体现。希望今天的教程能帮助你更好地理解它们,并在你的下一个项目中灵活运用。如果你对更多 Python 高阶话题感兴趣,欢迎到 云栈社区 与大家交流探讨。




上一篇:Web安全中的弱口令与暴力破解攻击原理与实战防护
下一篇:GitHub供应链投毒攻击实录:Node.js与Python项目遭GlassWorm恶意代码入侵
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-17 07:07 , Processed in 0.655716 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表