python学习笔记 | 一次使用装饰器时引发的思考

前言

今天使用装饰器时,遇到这样一个错误:

image-20210413161155560

根据报错提示,大意是指不能打包一个空值。而我写的装饰器是这样的:

def catch_error(func):
    def wrapper(*args,**kwargs):
        try:
            print(f'正在执行 - {func.__name__}')
            func(*args,**kwargs)
        except Exception as e:
            print(e)
    return wrapper

而当我不加上装饰器,单独运行函数的时候,却可以正常执行,并得到函数返回值

于是这时候问题的矛头就指向了我写的这个装饰器,既然报错提示为空值,那我就看一下这个函数返回值有没有问题,正常的返回值应该是「两个列表」

image-20210413163953111

这就很奇怪了,为什么是“None”??👃

于是我进一步检查一下现在的这个函数名

image-20210413164244996

???函数名不应该是"get_aim_note"吗?怎么会是"wrapper"?BTW 这个"wrapper"不是我在装饰器中声明的函数吗?怎么跑到这儿来了

云雾初开

OK,分析完问题,我们得到了两个信息:

  1. 被装饰后的函数其实不是原来的函数了,而是装饰器中声明的函数,也就是这个"wrapper",因此我在后续调用这个函数的时候,这个函数的返回对象其实是这个“wrapper”函数,而不是本来被装饰的函数,也难怪会返回一个None了。image-20210413174746173

  2. 被装饰后的函数其实已经是另外一个函数了,函数名等函数属性,会发生改变

拨云见日

  1. 第一个问题,我们本意是想得到「被装饰函数」的对象,却得到了装饰器中的嵌套函数“wrapper”。针对这个问题,我的思路是:在「wrapper函数体内」返回(return)「被装饰函数对象」

    def catch_error(func):
      def wrapper(*args,**kwargs):
        try:
          print(f"正在运行 - {func.__name__}")
          return func(*args,**kwargs)
        except Exception as e:
          print(e)
      return wrapper
    

    在第五行的位置我修改为return func(*args,**kwargs),通过这个操作,装饰器装饰函数的时候,返回的是wrapper函数的对象,而wrapper函数,它现在也有返回值了,就是这个func(*args,**kwargs),也就是我们最开始的「被装饰函数」。

    有点绕哈哈,,,㊙️

  2. 第二个问题,被装饰之后,「函数名等函数属性」会发生改变。针对这个问题,python的functools包提供了一个叫"wraps"的装饰器来消除这样的副作用,它能保留原有函数的名称和docstring - from functools import wraps

    from functools import wraps
    
    def catch_error(func):
        @wraps(func) # wraps装饰器
        def wrapper(*args,**kwargs):
            try:
                print(f'正在执行 - {func.__name__}')
                return func(*args,**kwargs) # 
            except Exception as e:
                print(e)
        return wrapper
    

结语

python的装饰器功能十分具有魅力,期待在后续的使用中发现更多的玩法。

更多 - python学习笔记 | 装饰器


1004 字