Python-闭包函数闭包捕获问题(类装饰器)

总结:类装饰器中使用闭包函数,出现了闭包捕获问题,出现原因是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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值