总结:类装饰器中使用闭包函数,出现了闭包捕获问题,出现原因是for循环中,外函数的局部变量的捕获,是捕获的该变量的引用,内包函数捕获的外函数局部变量不对,报错
报错代码展示:
class Logger():
def __init__(self):
self.logger=logging.getLogger()
self.file_handler=logging.FileHandler(filename="20241208.log")
format_handler=logging.Formatter('%(asctime)s-%(name)s-%(levelname)s-%(message)s')
self.file_handler.setFormatter(format_handler)
self.logger.addHandler(self.file_handler)
self.logger.setLevel(logging.DEBUG)
logger=Logger()
# 类装饰器
def log_class_decorator(cls):
for name,method in cls.__dict__.items():
# print(method)
if callable(method):# 确保 method 是可调用的
print(f"Decorating method: {method},")# 调试输出,查看正在装饰的方法
print(cls.__dict__)
# 定义 wrapper 方法时,确保 'method' 保持正确引用
def wrapper(self,*args,**kwargs):
result=method(self,*args,**kwargs)
# 日志记录传递给方法的参数和结果
if args:
logger.info(f"{args}")
if kwargs:
logger.info(f"{kwargs}")
logger.info(f'{result}')
return result
setattr(cls,name,wrapper)# 给类动态添加替换后的方法
return cls
运行之后,报错TypeError: 'NoneType' object is not callable
报错位置和信息:
result=method(self,*args,**kwargs)
TypeError: 'NoneType' object is not callable
经分析,这个代码整体是一个闭包函数,闭包函数一般包括内函数和外函数,内函数wrapper执行完后会返回本身,外函数是装饰器函数,
闭包的特点
- 外部函数返回了一个内部函数:闭包的核心特征是外部函数返回了内部函数,内部函数引用了外部函数中的变量。
- 保持对外部变量的引用:即使外部函数已经结束,闭包也能继续访问外部函数的局部变量。
例如举个例子:
def outer_function(outer_var):
# 这是外部函数的局部变量
def inner_function(inner_var):
# 这是内部函数
return outer_var + inner_var
return inner_function # 返回内函数
# 创建闭包
closure_function = outer_function(10)
# 调用闭包
print(closure_function(5)) # 输出 15
解析:
outer_function是外部函数,outer_var是它的局部变量。inner_function是内部函数,它访问了外部函数的outer_var变量。outer_function返回了inner_function,所以closure_function是一个闭包,它保留了outer_var的值(10),并能够访问它。
即使 outer_function 已经执行完毕并返回,closure_function 依然能访问到 outer_var,因为它形成了一个闭包。
在当前的代码中,wrapper 函数是在装饰器内定义的,它访问了外部的 method。然而,由于 method 是在 for 循环中定义的,所有 wrapper 函数都将捕获同一个 method 变量的引用。这可能导致所有 wrapper 都使用同一个 method(即最后一个被装饰的方法),而不是正确地使用每个方法的引用。
method是引用,那么由于 method 是在 for 循环中定义的,所有 wrapper 函数都将捕获同一个 method 变量的引用。这可能导致所有 wrapper 都使用同一个 method(即最后一个被装饰的方法),而不是正确地使用每个方法的引用。
改进方法:1.
functools.wraps(method):
- 这个装饰器用于确保在
wrapper函数中保留原始方法的元信息(如方法名称、文档字符串等)。没有这个装饰器的话,原方法的元信息将会丢失。 - 修改后的代码:
logger = Logger() # 类装饰器 def log_class_decorator(cls): for name,method in cls.__dict__.items(): if callable(method):# 确保 method 是可调用的 # 使用 wraps 保留原方法的元信息 @wraps(method) def wrapper(self, *args, __method=method, **kwargs): result = __method(self, *args, **kwargs) # 日志记录传递给方法的参数和结果 if args: logger.info(f"{args}") # logger.info(*args[1]) if kwargs: logger.info(f"{kwargs}") logger.info(f'{result}') return result setattr(cls,name,wrapper) return cls# 使用 wraps 保留原方法的元信息
@wraps(method)
def wrapper(self, *args, __method=method, **kwargs):
result = __method(self, *args, **kwargs)
思考:对于以下代码,会输出什么呢?
def test():
for i in range(3):
def wrapper():
print(i)
wrapper()
test()
改进方法2:
使用 functools.partial 确保 method 被正确引用
functools.partial 可以通过为 wrapper 创建一个新的函数对象,确保每个方法都得到正确的引用:
# 使用 partial 确保每个 wrapper 都正确引用对应的 method
decorated_method = partial(wrapper, method=method)
setattr(cls, name, decorated_method)
798

被折叠的 条评论
为什么被折叠?



