python 装饰器

栏目: Python · 发布时间: 5年前

内容简介:在python中,这里now函数被赋值给了f变量,调用now函数,其实就是进行f的调用。平时来看,函数一般都有入参(即函数的局部变量),而因为变量能代表函数,故函数入参为函数也就好理解了。故对实现了装饰器的函数,它的入参为函数也就不奇怪了。在python中当一个函数a就将另一个函数b作为参数,那么这个函数a就称之为可以看到函数log它最终的返回值是一个函数wrapper。在python中,返回函数指:“对于一个函数c,它最后的返回值是一个函数d,那么函数c就是一个返回函数”。返回函数的好处就是当调用c函数时

python 中, “函数也是一种对象” ,这句话是理解装饰器的基础。因为函数是一种对象,并且对象能被赋值给变量,所以函数就能被赋值给变量。例如:

>>> def now():
...     print('2015-3-25')
...
>>> f = now
>>> f()
复制代码

这里now函数被赋值给了f变量,调用now函数,其实就是进行f的调用。平时来看,函数一般都有入参(即函数的局部变量),而因为变量能代表函数,故函数入参为函数也就好理解了。故对实现了装饰器的函数,它的入参为函数也就不奇怪了。在python中当一个函数a就将另一个函数b作为参数,那么这个函数a就称之为 高阶函数 。而上面的函数log将func函数作入参,那么log就是一个高阶函数。

返回函数

可以看到函数log它最终的返回值是一个函数wrapper。在python中,返回函数指:“对于一个函数c,它最后的返回值是一个函数d,那么函数c就是一个返回函数”。返回函数的好处就是当调用c函数时不用立即执行c中的所有逻辑,我们可以看下面的例子:

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum
复制代码

函数lazy_sum实现了一个求和的功能,当我们调用lazy_sum时,其返回的不是直接的求和值,而是一个函数sum,

>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
复制代码

这里f变量其实就是sum函数,所以要求和的值时,调用f才可以。

>>> f()
25
复制代码

“当有一个函数h,它定义了内部函数g,最后返回了函数g,并且在该过程中g还使用了h函数的局部变量,那么这种程序python中称之为 闭包 ”。根据闭包定义,其实它也是一种返回函数,只不过它的内部函数使用了外部函数的局部变量(入参)。

装饰器理解

装饰器剖析

有了前面的知识储备,再来看最开始实现了装饰器功能的函数log:

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
复制代码

可以看到log函数的入参为func函数,根据定义,log函数就是一个高阶函数。再看log函数的返回值为函数wrapper,根据定义,log函数也是一个返回函数。如果把func看作一个变量,那么内部的函数wrapper使用了外部函数log的变量,根据定义,这个log函数它的程序也是一个闭包。 此刻我们对于实现装饰器功能的程序就有了一个整体认识,它既是一个高阶函数也是一个返回函数。当然整体上看它也是一个闭包。

装饰器的运行过程

在python中,当我们使用装饰器:

@log
def now():
    print('2015-3-25')


now()
复制代码

now()运行过程,实际上相当于执行了语句:

now = log(now)
复制代码

用了一个同原来now函数名字相同的变量now,(为了好区分,我们后面称变量now为now'吧。)来表示log函数的返回值。 这里log函数的返回值是函数wrapper,前面讲过函数是变量,变量能表示函数,此时now'变量表示了函数wrapper,即 now' = wrapper ,now()的执行相当于执行了wrapper函数,即 now'() 。 表面上看加了装饰器的now函数,其调用跟不加装饰器正常调用now函数是一样的,但经过上面我分析实际上并并不是,它实际上执行的是 now' = log(now); now'() 这两步。那句广告语“看起来一样一样,实际就是不一样...”,哈哈,说的就是这个过程。

带参数的装饰器

有时候我们给某个函数动态添加了功能,但又希望我们添加的功能对于我们的需求是可定制的。因此则有必要给装饰器添加参数变量。python中给装饰器添加参数的实现是这样的:

def log2(text):
    def log(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return log
复制代码

用法则如下:

@log2('execute')
def now():
    print('2015-3-25')
复制代码
>>> now()
execute now():
2015-3-25
复制代码

直观上看,其实就是给log函数外部再加了一层函数。根据前面的函数定义,不难看出log2是一个返回函数。而now()的运行过程则相当于执行了下面的语句:

now = log2('execute')(now)
复制代码

用了一个同原来now函数名字相同的变量now,(为了好区分,我们也称变量now为now'吧。)来表示log函数的返回值 。这里为什么now'表示的是log函数的返回值,而不是log2的返回值?相信聪明的你一眼也能得到答案哈!我们看, log2('execute') 这个函数的执行,实际上是返回了log函数,我们假设将返回的log函数赋值给变量f,那么 log2('execute')(now) 的执行其实就相当于 f(now) 的执行,而 f(now) 的执行,其返回值就是log函数的返回值即wrapper函数。所以这里now'其实表示的是wrapper函数。 可以看到无论是带参数还是不带参的装饰器,其表面上看now()的执行是执行了now,实际上都是执行wrapper函数。不相信的小伙伴可以打印下now的名称(即now.__name__)试一下,其值都是wrapper。表面上看python将now()的执行伪装了,但终究“狐狸的尾巴藏不住啊”,打印下它的函数名称,它就显露原形了。那么为了在平时使用中保持一致性,即加不加装饰器,打印“now.__name__”时都可以得到now函数的原始名字now',python也给了我们方法,即使用Python内置的 functools.wraps 即可。不带参和带参的方法各如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
复制代码
import functools

def log2(text):
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return log
复制代码

可以看到,其@functools.wraps(func)语句都是在wrapper函数上面加的。

总结

当然python的装饰器使用不止这些。这里只是最基本的理解(若有理解错的,请各位大佬指正哈)。对装饰器的理解首先是要知道 函数也是对象 ,其次只要弄清楚一些函数定义就可以了。当然这里还是得感谢 廖雪峰大神的python3教程 ,正是因为他那清晰透彻的教程,才使我理解装饰器的概念。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JUnit Recipes中文版

JUnit Recipes中文版

陈浩等译 / 电子工业 / 2006-9 / 69.00元

《JUnit Recipes中文版:程序员实用测试技巧》主要介绍了在Java开发中使用JUnit进行单元测试的各种方法、原则、技巧与实践。本书出自开发一线专家之手,本着实用的原则,涵盖各类Java开发中应用JUnit的实用技巧,内容丰富、全面深入;无论对于需要应用JUnit进行单元测试的一线Java开发人员,还是JUnit入门、进阶者,本书都是一本不可多得的实用指南。这本书介绍了大量的JUnit实......一起来看看 《JUnit Recipes中文版》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具