zoukankan      html  css  js  c++  java
  • 【python】装饰器听了N次也没印象,读完这篇你就懂了

    装饰器其实一直是我的一个"老大难"。这个知识点就放在那,但是拖延症。。。

    其实在平常写写脚本的过程中,这个知识点你可能用到不多

    但在面试的时候,这可是一个高频问题。

    一、什么是装饰器

    所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。

    这一句话理解起来可能没那么轻松,那先来看一个"傻瓜"函数。

    放心,绝对不是"Hello World"!

    def hello():
        print("你好,装饰器")
    

    肿么样,木骗你吧? 哈哈,这个函数不用运行相信大家都知道输出结果:"你好,装饰器"

    那如果我想让hello()函数再实现个其他功能,比如多打印一句话。

    那么,可以这样"增强"一下:

    def my_decorator(func):
        def wrapper():
            print("这是装饰后具有的新输出")
            func()
        return wrapper
    
    def hello():
        print("你好,装饰器")
    
    hello = my_decorator(hello)
    
    hello()
    

    运行结果:

    这是装饰后具有的新输出
    你好,装饰器
    [Finished in 0.1s]
    

    很显然,这个"增强"没啥作用,但是可以帮助理解装饰器。

    当运行最后的hello()函数时,调用过程是这样的:

    1. hello = my_decorator(hello)中,变量hello指向的是my_decorator()
    2. my_decorator(func)中传参是hello,返回的wrapper,因此又会调用到原函数hello()
    3. 于是乎,先打印出了wrapper()函数里的,然后才打印出hello()函数里的

    那上述代码里的my_decorator()就是一个装饰器。
    它改变了hello()的行为,但是并没有去真正的改变hello()函数的内部实现。

    但是,python一直以"优雅"被人追捧,而上述的代码显然不够优雅。

    二、优雅的装饰器

    所以,想让上述装饰器变得优雅,可以这样写:

    def my_decorator(func):
        def wrapper():
            print("这是装饰后具有的新输出")
            func()
        return wrapper
    
    @my_decorator
    def hello():
        print("你好,装饰器")
    
    hello()
    

    这里的@my_decorator就相当于旧代码的hello = my_decorator(hello)@符号称为语法糖。

    那如果还有其他函数也需要加上类似的装饰,直接在函数的上方加上@my_decorator就可以,大大提高函数
    的重复利用与可读性。

    def my_decorator(func):
        def wrapper():
            print("这是装饰后具有的新输出")
            func()
        return wrapper
    
    @my_decorator
    def hello():
        print("你好,装饰器")
    
    @my_decorator
    def hello2():
        print("你好,装饰器2")
    
    hello2()
    

    输出:

    这是装饰后具有的新输出
    你好,装饰器2
    [Finished in 0.1s]
    

    三、带参数的装饰器

    1. 单个参数

    上面的只是一个非常简单的装饰器,但是实际场景中,很多函数都是要带有参数的,比如hello(people_name)。

    其实也很简单,要什么我们就给什么呗,直接在对应装饰器的wrapper()上,加上对应的参数:

    def my_decorator(func):
        def wrapper(people_name):
            print("这是装饰后具有的新输出")
            func(people_name)
        return wrapper
    
    @my_decorator
    def hello(people_name):
        print("你好,{}".format(people_name))
    
    hello("张三")
    

    输出:

    这是装饰后具有的新输出
    你好,张三
    [Finished in 0.1s]
    

    2. 多个参数

    但是还没完,这样虽然简单,但是随之而来另一个问题:因为并不是所有函数参数都是一样的,
    当其他要使用装饰器的函数参数不止这个一个肿么办?比如:

    @my_decorator
    def hello3(speaker, listener):
        print("{}对{}说你好!".format(speaker, listener))
    

    没关系,在python里,*args**kwargs表示接受任意数量和类型的参数,所以我们可以这样
    写装饰器里的wrapper()函数:

    def my_decorator(func):
        def wrapper(*args, **kwargs):
            print("这是装饰后具有的新输出")
            func(*args, **kwargs)
        return wrapper
    
    @my_decorator
    def hello(people_name):
        print("你好,{}".format(people_name))
    
    @my_decorator
    def hello3(speaker, listener):
        print("{}对{}说你好!".format(speaker, listener))
    
    hello("老王")
    print("------------------------")
    hello3("张三", "李四")
    

    同时运行下hello("老王"),和hello3("张三", "李四"),看结果:

    这是装饰后具有的新输出
    你好,老王
    ------------------------
    这是装饰后具有的新输出
    张三对李四说你好!
    [Finished in 0.1s]
    

    3. 自定义参数

    上面2种,装饰器都是接收外来的参数,其实装饰器还可以接收自己的参数。
    比如,我加个参数来控制下装饰器中打印信息的次数:

    def count(num):
        def my_decorator(func):
            def wrapper(*args, **kwargs):
                for i in range(num):
                    print("这是装饰后具有的新输出")
                    func(*args, **kwargs)
            return wrapper
        return my_decorator
    
    @count(3)
    def hello(people_name):
        print("你好,{}".format(people_name))
    
    hello("老王")
    

    注意,这里count装饰函数中的2个return.
    运行下,应该会出现3次:

    这是装饰后具有的新输出
    你好,老王
    这是装饰后具有的新输出
    你好,老王
    这是装饰后具有的新输出
    你好,老王
    [Finished in 0.1s]
    

    4. 内置装饰器@functools.wrap

    现在多做一步探索,我们来打印下下面例子中的hello()函数的元信息:

    def my_decorator(func):
        def wrapper(*args, **kwargs):
            print("这是装饰后具有的新输出")
            func(*args, **kwargs)
        return wrapper
    
    @my_decorator
    def hello(people_name):
        print("你好,{}".format(people_name))
    
    print(hello.__name__) #看下hello函数的元信息
    

    输出:

    wrapper
    

    这说明了,它不再是以前的那个 hello() 函数,而是被 wrapper() 函数取代了。

    如果我们需要用到元函数信息,那怎么保留它呢?这时候可以用内置装饰器@functools.wrap

    import functools
    
    def my_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print("这是装饰后具有的新输出")
            func(*args, **kwargs)
        return wrapper
    
    @my_decorator
    def hello(people_name):
        print("你好,{}".format(people_name))
    
    print(hello.__name__)
    

    运行下:

    hello
    [Finished in 0.1s]
    

    四、类装饰器

    装饰器除了是函数之外,也可以是类。

    但是类作为装饰器的话,需要依赖一个函数__call__(),当调用这个类的实例时,函数__call__()
    会被执行。

    来改造下之前的例子,把函数装饰器改成类装饰器:

    class MyDecorator():
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            print("这是装饰后具有的新输出")
            return self.func(*args, **kwargs)
    
    # def my_decorator(func):
    #     def wrapper():
    #         print("这是装饰后具有的新输出")
    #         func()
    #     return wrapper
    
    @MyDecorator
    def hello():
        print("你好,装饰器")
    
    hello()
    

    运行:

    这是装饰后具有的新输出
    你好,装饰器
    [Finished in 0.1s]
    

    跟函数装饰器一样,实现一样的功能。

    五、装饰器的嵌套

    既然装饰器可以增强函数的功能,那如果有多个装饰器,我都想要怎么办?
    其实,只要把需要用的装饰器都加上去就好了:

    @decorator1
    @decorator2
    @decorator3
    def hello():
        ...
    

    但是要注意这里的执行顺序,会从上到下去执行,可以来看下:

    
    def my_decorator(func):
        def wrapper():
            print("这是装饰后具有的新输出")
            func()
        return wrapper
    
    def my_decorator2(func):
        def wrapper():
            print("这是装饰后具有的新输出2")
            func()
        return wrapper
    
    def my_decorator3(func):
        def wrapper():
            print("这是装饰后具有的新输出3")
            func()
        return wrapper
    
    @my_decorator
    @my_decorator2
    @my_decorator3
    def hello():
        print("你好,装饰器")
    
    hello()
    

    运行

    这是装饰后具有的新输出
    这是装饰后具有的新输出2
    这是装饰后具有的新输出3
    你好,装饰器
    [Finished in 0.1s]
    

    好记性不如烂笔头,写一下理解一下会好很多。

  • 相关阅读:
    springboot文件上传: 单个文件上传 和 多个文件上传
    Eclipse:很不错的插件-devStyle,将你的eclipse变成idea风格
    springboot项目搭建:结构和入门程序
    POJ 3169 Layout 差分约束系统
    POJ 3723 Conscription 最小生成树
    POJ 3255 Roadblocks 次短路
    UVA 11367 Full Tank? 最短路
    UVA 10269 Adventure of Super Mario 最短路
    UVA 10603 Fill 最短路
    POJ 2431 Expedition 优先队列
  • 原文地址:https://www.cnblogs.com/pingguo-softwaretesting/p/13522909.html
Copyright © 2011-2022 走看看