美文网首页
Python装饰器

Python装饰器

作者: 什么23 | 来源:发表于2019-11-13 01:49 被阅读0次
    # 这是一个装饰器的简单例子
    
    @dec
    def func():
        pass
    

    什么是装饰器

    装饰器可以在不改变原有对象的代码及调用方式的情况下,为原有的对象增加新的功能或限制条件。装饰器有函数装饰器,也有类装饰器。装饰器体现的是开放封闭原则,即对功能扩展的开放,对修改已实现功能的封闭。

    装饰器本质上就是Python的函数,它和普通的函数没有任何区别,只是有了特殊的用法,所以在特殊用法下就有了装饰器这个名字。

    装饰器怎么用

    现在有一个hello函数,功能很简单,只是打印hello world!

    def hello():
        print('hello world!')
        
    hello()
    

    它的执行结果为:

    hello world!
    

    现在要对这个函数进行一下功能的扩展。

    def dec(func): # 这个就是装饰器,参数就是被装饰的函数
        def wrapper():
            print('start...')
            func()
            print('end...')
        return wrapper
        
    @dec # 这是装饰器的用法
    def hello(): 
        print('hello world!')
        
    hello() #函数的调用方法和上边一样没有改变    
    

    现在的执行结果为:

    start...
    hello world!
    end...
    

    如上面的代码显示,函数dec就是装饰器,函数hello就是被装饰的函数。而装饰器的用法,就是hello函数上面的@dec。注意,这里应用装饰器只是写了dec这个函数名,而不是dec()这样调用它。

    装饰器的运行机制

    还是上面的例子。

    1. 解释器读到函数dec,也就是装饰器。这时它还没有被调用,所以只是被存在了内存中,并没有被执行。
    2. 到了@dec。@dec和dec()一样,都会执行这个函数,但是不同的是,@dec会把被装饰的hello函数的本身(而不是函数执行结果)当作参数,传入到dec函数中,相当于执行了dec(hello)这个函数。
    3. 这时开始执行dec函数。首先会把wrapper函数存入到内存中(并不是执行),然后返回wrapper函数本身(也不是执行)给hello函数。也就是,hello函数被装饰完之后,内存地址被指向到了wrapper函数的内存地址。
    4. 执行hello函数。由于hello函数的地址已经被指向到了wrapper函数,所以执行结果就是wrapper函数的执行结果。wrapper函数中的func这个函数地址才是原来hello函数的内存地址。所以使用装饰器之后,表面上不会改变原来函数调用的方法。

    以上就是装饰器的运行机制。

    为什么是嵌套函数

    刚开始学习装饰器的人可能会比较疑惑,装饰器为什么要用嵌套函数?感觉好多功能不用嵌套也可以实现。下面看例子。

    def dec(func):    
        print('start...')    
        func()    
        print('end...')
    
    @dec
    def hello():    
        print('hello world!')
    

    执行结果为:

    start...
    hello world!
    end...
    

    这个执行结果看起来没有问题。是的,执行结果是没有问题,但是问题是还没有写执行语句却已经有了执行结果。

    造成这个问题的原因就在@dec这里。@dec就是执行装饰器的语句,所以它就执行了dec这个函数。这就是为什么,还没有写执行语句却有了执行结果的原因。这也是为什么装饰器要用嵌套函数的原因,再用一个函数封装一下,避免在定义阶段就给执行了。

    被装饰的函数带参数

    def hello(string):
        print(f'hello {string}!')
        
        
    hello('python')
    

    执行结果为:

    hello python!
    

    这是一个带参数的函数,那么这种函数如何添加装饰器呢?

    其实,只要理解了被装饰的函数会被指向到装饰器里的函数,那么在装饰器里的函数加上参数就解决了传参的问题。下面看例子。

    def dec(func):
        def wrapper(string): # 添加和hello一样的参数即可
            print('start...')
            func(string) # 通过wrapper函数传递来的参数
            print('end...')
        return wrapper
    
    @dec
    def hello(string): # 内存地址会被指向wrapper的内存地址
        print(f'hello {string}!')
    
    hello('python')
    

    执行的结果为:

    hello python!
    

    装饰器带参数

    如果理解了前边的内容,那么理解装饰器带参数也就比较容易了。下面先看例子。

    def outer(name): # 多了一层函数,用于接收传递装饰器的参数
        def dec(func):
            def wrapper(string):
                print(f'{name} start...')
                func(string)
                print(f'{name} end...')
            return wrapper
        return dec
    
    @outer('haha')  # 装饰器执行语句多了括号和参数
    def hello(string):
        print(f'hello {string}!')
    
    hello('python')
    

    执行结果为:

    haha start...
    hello python!
    haha end...
    

    首先可以看到,装饰器在定义的时候又多了一层嵌套,这层嵌套用于接收传递参数,并且返回下一层的嵌套函数。

    然后可以看到装饰器多了括号和参数。现在说的就是带参数的装饰器,所以带参数没有什么稀奇的。

    最主要的是多的那个括号。装饰器执行语句多了括号之后,它的意思也稍微有了变化。我们知道,执行函数就是函数名加上括号。那么 @outer('haha') 的执行顺序是下面这样的。

    1. 先执行outer('haha')这个函数,这时它的参数是'haha'。
    2. outer函数返回值是dec,所以@outer('haha') = @dec,并把参数传到了函数内。
    3. @dec这个语句就是我们前边熟悉的装饰器的执行语句了。它会把hello函数本身当作参数传递给函数dec,并把内存地址指向到了wrapper函数。后边执行过程就跟前边的例子一样,也就不再赘述了。

    主要知道@outer()会先执行outer函数,而不是把hello当作参数的装饰器语句,那么对于带参数的装饰器也就没什么难点了。

    叠加装饰器

    装饰器也可以叠加使用,还是先看例子。

    def dec1(func):
        print('这是第一层')
        def wrapper():
            print('这是第1层开始')
            func()
            print('这是第1层结束')
        return wrapper
    
    def dec2(func):
        print('这是第二层')
        def wrapper():
            print('这是第2层开始')
            func()
            print('这是第2层结束')
        return wrapper
    
    @dec2
    @dec1
    def hello():
        print('hello world!')
    
    print('开始')
    hello()
    

    执行结果为:

    这是第一层
    这是第二层
    开始
    这是第2层开始
    这是第1层开始
    hello world!
    这是第1层结束
    这是第2层结束
    

    这个结果可能刚开始看有些不那么好理解,我们慢慢看。

    先看执行结果的前三行。这三行内容里,首先执行的是两个装饰器的内容,之后才是print语句。从这个执行结果可以看出,python是从上到下顺序执行,并在遇到装饰器执行语句的时候会自动执行装饰器函数('第几层开始'没有跟着执行是因为被封装进了函数,要执行语句才能执行)。再从首先执行了第一层又执行了第二层可以看出,遇到叠加装饰器时是从下向上执行的。

    明白了装饰器是从下向上执行,那么就是下一层装饰器的函数就是它上一层装饰器的参数。

    @dec2  # 相当与dec2(dec1(hello))
    @dec1  # 相当于dec1(hello)
    def hello():
    

    这么看这个执行结果就好理解了。

    结束

    这篇文章主要是帮助初步理解装饰器和一些简单的用法,在这里就不详细说明其他更深奥的用法了(主要是我不会),那么这篇文章也就到这里了。

    相关文章

      网友评论

          本文标题:Python装饰器

          本文链接:https://www.haomeiwen.com/subject/lkbcictx.html