Discuss / Python / 装饰器使用方法全面解析

装饰器使用方法全面解析

Topic source

这一章其实要吃透还是不容易的,因为涉及到4类构造器。不过廖老师似乎没有按照这样的逻辑来讲,我在自学的过程中整理了四类构造器的使用方法和心得,希望对大家有帮助!具体可以看代码!P1 P2 P3 P4代表了四类构造器,P5是对该题目的解答!

__author__ = 'Kaiming'


# 装饰器按照装饰函数是否带参数和被装饰函数是否带参数可以分为4类


import functools


# P1——两者都不带参数

# 定义装饰函数,被装饰函数作为参数
def decorator_one(f1):
    print('start')
    print('call ' + f1.__name__)
    print('end')
    # 被装饰函数不带参数,返回被装饰函数,最外层函数必须接受一个返回函数
    return f1


@decorator_one
# 采取和上面定义不同的名字,否则会出错
def f1():
    pass


f1()


# P2——装饰函数不带参数,被装饰函数带参数

# 定义装饰函数,被装饰函数作为参数
def decorator_two(f2):
    # 定义包装函数,用于传递给被装饰函数当做参数,使用可变参数和关键字参数可以提升灵活性
    def wrapper_two(*args, **kw):
        print('start')
        print('call' + f2.__name__)
        f2(*args, **kw)
        print('end')

    # 这里的return返回的是decorator_two这个函数的返回值
    return wrapper_two


@decorator_two
def f2(s):
    print(s)


'''流程是:f2=decorator_two(f2),然后运行wrapper_two函数,在f2执行前后进行
处理,最后还要把函数最为一个返回值,这个是装饰函数要求的'''

f2('被装饰函数f2的参数')


# P3——装饰函数带参数,被装饰函数不带参数

# 装饰函数外层加一层函数,用来传递装饰函数的参数
def my_decorator_three(*args, **kw):
    # 这里才是装饰函数开始的地方,传入装饰函数参数f3
    def decorator_three(f3):
        #    @functools.wraps(f3) 这句在这里可加可不加,反正不会出现函数名字指定错误
        print('start')
        print('f3的装饰器函数自带参数为:', *args)
        print('call ' + f3.__name__)
        print('end')
        # 这里需要返回被装饰函数本身f3,当做
        return f3

    return decorator_three


'''注意P3的调用逻辑是f3=my_decorator_three(*args,**kw)(f3)(),最后的(f3)在这里是
使用前面的返回函数decorator_three(f3),可以完成装饰函数自带参数实现的功能,最后的()实际上
再使用不带参数的f3()函数本身完成被装饰函数本身的功能'''


# 这里采用最外层的函数
@my_decorator_three('my_decorator_three')
def f3():
    pass


f3()


# P4——装饰器函数和被装饰函数都带参数

# 这层传递的参数是装饰函数本身需要使用的参数
def my_decorator_four(*args_decorator, **kw_decorator):
    # 这一层函数传递需要被装饰的函数f4
    def decorator_four(f4):
        @functools.wraps(f4)
        # 这一层包装函数用于传递被装饰函数f4所需要的参数
        def wrapper(*args, **kw):
            print('start')
            print(f4.__name__ + ' is working')
            # 在这个函数内已经使用了带参数的被装饰函数f4,所以最后也不需要返回函数f4了
            f4(*args, **kw)
            print('end')

        return wrapper

    return decorator_four


'''P4的调用逻辑是f4=my_decorator_four(**args_decorator,**kw_decorator)(f4)(*args,**kw);这个逻辑比较长,首
先是他用最外层的函数,传递装饰函数需要的参数;然后用于最外层函数返回的函数是decorator_four(),所以
后面的(f4)实际上等价于decorator_four(f4),然后由于decorator_four函数的返回值是wrapper,所以(*args,**kw)
等价于wrapper(*args,**kw)'''


@my_decorator_four('my_decorator_four')
def f4(s):
    print(s)


f4('被装饰函数f4的参数')


# P5 课后习题,通过分析课后习题可以知道,需要设计一个装饰函数带可变参数,被装饰函数不带参数的装饰器

# 为了保持前后的统一性,函数名我就不用log了,而是用my_decorator_five代替log,f5代替f
def my_decorator_five(*args, **kw):
    def decorator_five(f5):
        print(*args)
        print('begin call')
        # 在函数中使用了被装饰函数,就不用再返回这个函数了,因为已经在这被执行过了
        f5()
        print('end call')

    return decorator_five


@my_decorator_five()
def f5():
    pass


@my_decorator_five('execute')
def f5():
    pass

'''P5的装饰器函数调用逻辑实际上和P3类似,f5=my_decorator_five(*args,**kw)(f5),好P3的差别就是最后不用加(),因
为最后没有返回被装饰函数'''

纠正下我上文说法中的错误,是装饰器,而不是构造器!不好意思 ^_^

冥想盆9052

#3 Created at ... [Delete] [Delete and Lock User]

感谢层主,你的详细剖析非常到位,一下子就明白了为何要用嵌套以及用几层嵌套,是对教程非常好的补充完善

冥想盆9052

#4 Created at ... [Delete] [Delete and Lock User]

但是我发现一个问题,照层主的写法,在P5里面,最后执行函数的时候,不能写成f5() , 而只能写成f5, 原因在于此时的 f5 = mydecoratorfive(args,*kw)(f5),而在函数decorator里面f5()已经被执行了,因此写成f5就可以被直接运行了; 这会导致一个问题,如果以后还想取f5的名字属性会出错,此时的f5已经不是一个名字了,而是类似 f()的东西,print(f().name) 必然出错,因此,此时print(f5.name)也会返回错误“AttributeError: 'NoneType' object has no attribute '__name'”

Wints

#5 Created at ... [Delete] [Delete and Lock User]

评论区里大神出没,给跪了!

独自宣言

#6 Created at ... [Delete] [Delete and Lock User]

这种思考方式非常有助于理解,但其实代码中的P1和P3并没有实现装饰器的“动态增加功能”:

比如可以看看P1这段代码:

# 定义装饰函数,被装饰函数作为参数
def decorator_one(f1):
    print('start')
    print('call ' + f1.__name__)
    print('end')
    # 被装饰函数不带参数,返回被装饰函数,最外层函数必须接受一个返回函数
    return f1

当接下来定义语法糖:

@decorator_one
# 采取和上面定义不同的名字,否则会出错
def f1():
    pass

的过程中时,这一段代码已经输出了:

start
call f1
end

然后返回了f1,当在下一步调用f1时,只会输出f1中的内容(pass),而不会动态增加以上的代码。

P3中的代码也是同理。

所以层主P5中的代码实际上是不符合课后习题的答案的。

W_D__

#7 Created at ... [Delete] [Delete and Lock User]

我想问一下,这个:

@decorator_one
# 采取和上面定义不同的名字,否则会出错
def f1():
    pass

是不是就等于:

def f1():
    pass
f1 = decorator_one(f1)

这时候是不是相当于调用了decorator_one(f1) 此时根据:

def decorator_one(f1):
    print('start')
    print('call ' + f1.__name__)
    print('end')
    # 被装饰函数不带参数,返回被装饰函数,最外层函数必须接受一个返回函数
    return f1

所以才会打印start这些

然后:

f1()

这时候,相当于调用:

f1()

因为 decorator_one(f1)的返回值就是f1,所以才不会打印?

W_D__

#8 Created at ... [Delete] [Delete and Lock User]

但是如果写成:

def decorator_one(f1):
    def wrapper:
    print('start')
    print('call ' + f1.__name__)
    f1()
    print('end')
    # 被装饰函数不带参数,返回被装饰函数,最外层函数必须接受一个返回函数
   return wrapper

这样根据刚刚说的,调用f1()就相当于调用wrapper,所以每次调用f1的时候会打印那些信息。而这个时候:

@decorator_one
# 采取和上面定义不同的名字,否则会出错
def f1():
    pass

仅仅是:

def f1():
    pass
f1 = decorator_one(f1)

decorator_one仅仅就是返回了wrapper,但并不调用,让f1指向了wrapper而已。请问是这样理解吗?

楼主写的很好,可是在文档字符串里的说明里面,函数返回的f4.name =f4而不是wrapper。

imustdoit

#10 Created at ... [Delete] [Delete and Lock User]

感谢分享


  • 1
  • 2

Reply