Python包装与代理模式——functools.wraps、委托代理、日志代理
包装和代理是Python中重要的设计模式。正确实现它们需要理解函数装饰器、属性委托和元编程技术。
import functools
import time
from typing import Any
# ========== functools.wraps 保护装饰器元数据 ==========
def bad_timer(func):
"""错误的计时装饰器——丢失函数元信息"""
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"执行耗时: {elapsed:.4f}秒")
return result
return wrapper # 返回的是wrapper,原函数信息丢失
def good_timer(func):
"""正确的计时装饰器——使用wraps保留函数信息"""
@functools.wraps(func) # 这步最关键的修复
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"耗时: {elapsed:.4f}秒")
return result
return wrapper
@bad_timer
def compute_something():
"""计算某些重要数据"""
return sum(range(1000))
@good_timer
def another_compute():
"""另一个重要计算函数"""
return sum(range(1000))
print(f"错误装饰器——函数名: {compute_something.__name__}") # wrapper,不是原名
print(f"错误装饰器——文档: {compute_something.__doc__}") # None
print(f"正确装饰器——函数名: {another_compute.__name__}") # another_compute
print(f"正确装饰器——文档: {another_compute.__doc__}") # 保留原文档
# ========== __getattr__ 委托代理 ==========
class Proxy:
"""通用代理:将属性访问转发给内部对象"""
def __init__(self, wrapped):
# 使用object.__setattr__避免触发__setattr__逻辑
object.__setattr__(self, '_wrapped', wrapped)
def __getattr__(self, name):
"""所有未找到的属性都委托给包装对象"""
wrapped = object.__getattribute__(self, '_wrapped')
try:
return getattr(wrapped, name)
except AttributeError:
raise AttributeError(f"代理对象没有属性 '{name}'")
def __setattr__(self, name, value):
"""设置属性时直接设置到包装对象上"""
if name == '_wrapped':
object.__setattr__(self, name, value)
else:
wrapped = object.__getattribute__(self, '_wrapped')
setattr(wrapped, name, value)
def __delattr__(self, name):
"""删除包装对象的属性"""
wrapped = object.__getattribute__(self, '_wrapped')
delattr(wrapped, name)
class DataStore:
"""被代理的目标类"""
def __init__(self):
self.items = []
def add(self, item):
self.items.append(item)
def total(self):
return len(self.items)
# 演示代理
store = DataStore()
proxy = Proxy(store)
proxy.add("数据1")
proxy.add("数据2")
print(f"代理访问total方法: {proxy.total()}")
# ========== __slots__ 与代理结合 ==========
class SlottedProxy:
"""使用__slots__减少内存消耗的代理"""
__slots__ = ('_wrapped',) # 只允许这两个属性
def __init__(self, wrapped):
object.__setattr__(self, '_wrapped', wrapped)
def __getattr__(self, name):
wrapped = object.__getattribute__(self, '_wrapped')
return getattr(wrapped, name)
def __setattr__(self, name, value):
if name == '_wrapped':
object.__setattr__(self, name, value)
else:
wrapped = object.__getattribute__(self, '_wrapped')
setattr(wrapped, name, value)
# ========== 日志代理模式 ==========
class LoggingProxy:
"""在属性访问前后添加日志的代理"""
def __init__(self, target, logger=None):
object.__setattr__(self, '_target', target)
object.__setattr__(self, '_log', logger or print)
def __getattr__(self, name):
target = object.__getattribute__(self, '_target')
log = object.__getattribute__(self, '_log')
log(f"代理读取: {name}")
attr = getattr(target, name)
# 如果属性是方法,包装它以便记录调用
if callable(attr) and not name.startswith('_'):
@functools.wraps(attr)
def logged_method(*args, **kwargs):
log(f"调用方法: {name}(args={args}, kwargs={kwargs})")
result = attr(*args, **kwargs)
log(f"方法返回: {name} -> {result}")
return result
return logged_method
return attr
def __setattr__(self, name, value):
"""记录日志的setattr"""
if name in ('_target', '_log'):
object.__setattr__(self, name, value)
else:
target = object.__getattribute__(self, '_target')
log = object.__getattribute__(self, '_log')
log(f"代理设置属性: {name} = {value}")
setattr(target, name, value)
# 演示日志代理
ds = DataStore()
logged = LoggingProxy(ds)
logged.add("测试数据")
print(f"总数据量: {logged.total()}")
Python包装与代理模式
张小明
前端开发工程师
别再手动拖拽了!Fluent里这个‘相机’参数,才是高效对比仿真结果的秘密武器
别再手动拖拽了!Fluent里这个‘相机’参数,才是高效对比仿真结果的秘密武器 在CFD仿真工作中,最令人头疼的莫过于反复调整视角来对比不同方案的流场结果。明明是两个相似的涡旋结构,因为视角偏差导致评审会上被质疑数据可比性&…
[开源] 医保门特/慢病处方申诉批量审核工具:面向医保办人员的终端交互式审方系统
本项目是专为医院医保办公室设计的本地化终端(TUI)审方工具,用于高效处理门特(门诊特殊病)、慢病(门诊慢性病)处方申诉批次。我们不依赖网页系统、不上传患者数据、不绑定特定云服务,…
Dell R720服务器风扇太吵?手把手教你用IPMI工具ipmitool在CentOS 8下调速(附Windows软件)
Dell R720服务器静音改造实战:从IPMI原理到安全调速指南深夜的工作室里,Dell PowerEdge R720服务器风扇的轰鸣声总是格外刺耳。这种工业级散热设计虽然保证了数据中心环境下的稳定运行,但对于家庭实验室或小型工作室而言,却成了影…
即梦去水印方法亲测:教程与4款工具横评
说实话,我用即梦AI跑图跑视频已经大半年了,最让我头疼的从来不是出图质量,而是右下角那个"即梦AI"的品牌水印。辛辛苦苦抽了几十次卡,终于出了一张满意的图,结果发出去带着水印,要么像在打广告&a…
Cookie复用实战:从百度网盘到12306,手把手教你绕过复杂验证码做自动化测试
Cookie复用技术在复杂验证场景下的自动化测试实战1. 验证码机制与Cookie复用的技术原理现代Web应用的安全防护体系中,验证码机制扮演着重要角色。从简单的数字验证码到复杂的滑块验证、行为验证甚至手机短信验证,这些机制在保护系统安全的同时࿰…
告别重复劳动:用小芒果连点器打造你的Windows自动化脚本(图文识别+变量指令)
告别重复劳动:用小芒果连点器打造你的Windows自动化脚本每天面对电脑重复点击、填写表格、处理数据,你是否感到效率低下又疲惫?小芒果连点器这款轻量级工具,正悄然改变着普通用户的自动化体验。不同于专业RPA软件动辄上千元的订阅…