python 装饰器

定义

关于python中装饰器的定义,我们这里参考廖雪峰大神的python3教程中的定义:在某个函数(代码)运行期间,在不更改该函数的功能下,动态给该函数添加功能的方式,我们称之为“装饰器”。从定义中看出,这个装饰器势必要在实现中传入原函数,并在其功能中使原函数功能不受影响。由此我们想到的一种实现装饰器的方法就是:“实现一个功能(可是一段代码/函数),该功能的实现要传入一个函数。”翻译过来就是通过一个函数(比如叫“a”函数)实现装饰器的功能,但是该函数a的入参需要传入的是一个函数(比如叫“b”函数)。那么想象中的装饰器实现方式是:

# 这里的b是一个函数
def a(b):
   b()
   pass
复制代码

而实际上python中基本的装饰器的模样是这样(注:文章中的大量定义和源码都参考廖雪峰大神的python教程):

# 这里的func是一个函数,与想象中的b函数一致,log就是上面中的a函数
def log(func):
# *args,**kw泛指函数func的入参
   def wrapper(*args, **kw):
       # __name__是python中的一个属性,它能返回对应函数的名称
       print('call %s():' % func.__name__)
       return func(*args, **kw)
   return wrapper
复制代码

要使用装饰器,python中的方法如下:

# 给函数上面加一个@符号,再接实现了装饰器功能的函数名称
@log
def now():
   print('2015-3-25')
复制代码

调用now()函数:

>>> now()
call now():
2015-3-25
复制代码

可以看到使用了装饰器,给now函数动态增加了打印它的名称的功能。且没有影响它的原始功能。 根据对比,python中装饰器的实现跟想象中的实现方式差不多,不过python中实现装饰器的函数log其返回值是一个函数wrapper,这个log是一个 返回函数 ,什么是返回函数?为了弄清楚这个log函数,我们还是先来看一些函数的基础知识吧

基础知识

函数也是一种对象

在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教程,正是因为他那清晰透彻的教程,才使我理解装饰器的概念。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值