news 2026/5/31 21:39:01

Python包装与代理模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python包装与代理模式

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()}")

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/29 13:03:03

即梦去水印方法亲测:教程与4款工具横评

说实话,我用即梦AI跑图跑视频已经大半年了,最让我头疼的从来不是出图质量,而是右下角那个"即梦AI"的品牌水印。辛辛苦苦抽了几十次卡,终于出了一张满意的图,结果发出去带着水印,要么像在打广告&a…

作者头像 李华