签到成功

知道了

CNDBA社区CNDBA社区

Python 装饰器 说明

2018-10-19 10:35 1911 0 原创 Python
作者: dave

1 装饰器概念

装饰器的作用是给某程序增添功能,因为该程序已经上线,不能直接修改源代码,因为就产生了装饰器,装饰器可以实现以下2点:
1) 不修改被装饰的函数的源代码
2) 不修改被装饰的函数的调用方式

装饰器的原则组成:< 函数+实参高阶函数+返回值高阶函数+嵌套函数+语法糖 = 装饰器 >

http://www.cndba.cn/cndba/dave/article/3090

在简单点说,装饰器由闭包和语法糖组成。

闭包:即两个函数嵌套,外部函数返回内部函数的引用,外部函数一定会传入参数,外部函数起的是交换引用的作用:把要装饰的参数(也就是装饰前的函数)与 装饰后的函数的引用对换,而里层的函数做的是执行操作,调用原函数就是在这里执行的。

闭包模型:

#外函数
def set_fun(func):
    def call_fun( *args, **kwargs):
#里函数 
        result = func(*args, **kwargs)
#这里可以对原函数的执行结果做筛选
        return result
    return call_fun

语法糖:

@set_fun  # @这个就是语法糖,可以理解为:set_fun ==>  test = set_fun(test)
def test():
    print("要装饰的原函数")

2 高阶函数

高阶函数的形式可以有两种:
1) 把一个函数名当作实参传给另外一个函数(“实参高阶函数”)
2) 返回值中包含函数名(“返回值高阶函数”)http://www.cndba.cn/cndba/dave/article/3090

这里面所说的函数名,实际上是函数的地址,如果可以把函数名当做实参,那么也就是说可以把函数传递到另一个函数,然后在另一个函数里面做一些操作,即不修改源代码而增加功能,实现了装饰器的功能。示例如下:

import time

def test():
    time.sleep(2)
    print("test is running!")

def deco(func):  
    start = time.time()
    func() #2
    stop = time.time()
    print(stop-start)

deco(test) #1

在#1处,我们把test当作实参传递给形参func,即func=test。注意这里传递的是地址,也就是此时func也指向了之前test所定义的那个函数体,可以说在deco()内部,func就是test。在#2处,把函数名后面加上括号,就是对函数的调用(执行它)。因此,这段代码运行结果是:
test is running!
2.00099992752http://www.cndba.cn/cndba/dave/article/3090

该写法没有修改被装饰的函数的源代码,但这种方式修改了调用方式。如果不修改调用方式,那么在这样的程序中,被装饰函数就无法传递到另一个装饰函数中去。

如果不修改调用方式,就是一定要有test()这条语句,那么就用到了第二种高阶函数,即返回值中包含函数名,如下代码:http://www.cndba.cn/cndba/dave/article/3090http://www.cndba.cn/cndba/dave/article/3090

# -*- coding: utf-8 -*-
import time

def test():
    time.sleep(2)
    print("test is running!")

def deco(func):
    print(func)
    return func

t = deco(test) #3
#t()#4

test()

这段代码中,在#3处,将test传入deco(),在deco()里面操作之后,最后返回了func,并赋值给t。因此这里test => func => t,都是一样的函数体。最后在#4处保留了原来的函数调用方式。 当然这里改成输出函数地址,因为,单独采用第二种高阶函数(返回值中包含函数名)的方式,并且保留原函数调用方式,是无法计时的。如果在deco()里计时,显然会执行一次,而外面已经调用了test(),会重复执行。

http://www.cndba.cn/cndba/dave/article/3090

3 嵌套函数

嵌套函数指的是在函数内部定义一个函数,而不是调用,如:

def func1():
    def func2():
        pass

而不是

def func1():
    func2()

函数只能调用和它同级别以及上级的变量或函数。也就是说:里面的能调用和它缩进一样的和他外部的,而内部的是无法调用的。

在看修改之后的代码:

# -*- coding: utf-8 -*-
import time

def test():
    time.sleep(2)
    print("中国DBA社区网址:https://www.cndba.cn")

def timer(func): #5
    def deco():
        start = time.time()
        func()
        stop = time.time()
        print(stop-start)
    return deco

test = timer(test) #6

test() #7

首先,在#6处,把test作为参数传递给了timer(),此时,在timer()内部,func = test,接下来,定义了一个deco()函数,但并未调用,只是在内存中保存了,并且标签为deco。在timer()函数的最后返回deco()的地址deco。然后再把deco赋值给了test,那么此时test已经不是原来的test了,也就是test原来的那些函数体的标签换掉了,换成了deco。那么在#7处调用的实际上是deco()。http://www.cndba.cn/cndba/dave/article/3090

所以这段代码在本质上是修改了调用函数,但在表面上并未修改调用方式,而且实现了附加功能。这里就是闭包的实现。把test传递到某个函数,而这个函数内恰巧内嵌了一个内函数,再根据内嵌函数的作用域(可以访问同级及以上,内嵌函数可以访问外部参数),把test包在这个内函数当中,一起返回,最后调用这个返回的函数。而test传递进入之后,再被包裹出来,显然test函数没有弄丢(在包裹里),那么外面剩下的这个test标签正好可以替代这个包裹(内含test())。

http://www.cndba.cn/cndba/dave/article/3090

4 最简单的装饰器

根据以上分析,装饰器在装饰时,需要在每个函数前面加上:
test = timer(test)
显然有些麻烦,Python提供了一种语法糖,即:
@timer
这两句是等价的,只要在函数前加上这句,就可以实现装饰作用。

# -*- coding: utf-8 -*-
import time

def deco(func):
    def wrapper():
        startTime = time.time()
        func()
        endTime = time.time()
        msecs = (endTime - startTime)*1000
        print("time is %d ms" %msecs)
    return wrapper

@deco
def func():
    print("hello")
    time.sleep(1)
    print("world")

if __name__ == '__main__':
    f = func #这里f被赋值为func,执行f()就是执行func()
    f()

5 装饰有参函数

在实际应用中,函数往往是有参数的,如果装饰器中没有对应的参数,那么程序在运行的时候,就会报错,所以对于有参数的函数,在装饰器中也需要加上对应的参数。

对于参数个数固定的情况,可以参考如下示例代码:

# -*- coding: utf-8 -*-
import time

def deco(func):
    def wrapper(a,b):
        startTime = time.time()
        func(a,b)
        endTime = time.time()
        msecs = (endTime - startTime)*1000
        print("time is %d ms" %msecs)
    return wrapper


@deco
def func(a,b):
    print("hello,here is a func for add :")
    time.sleep(1)
    print("result is %d" %(a+b))

if __name__ == '__main__':
    f = func
    f(3,4)
    #func()

如果函数的参数不确定,那么可以使用args, **kwargs。 关于这2个参数的说明,可以参考我的博客:
Python 函数中
args 和 **kwargs 的用法
https://www.cndba.cn/dave/article/3089

# -*- coding: utf-8 -*-
#带有不定参数的装饰器
import time

def deco(func):
    def wrapper(*args, **kwargs):
        startTime = time.time()
        func(*args, **kwargs)
        endTime = time.time()
        msecs = (endTime - startTime)*1000
        print("time is %d ms" %msecs)
    return wrapper


@deco
def func(a,b):
    print("中国DBA社区网址:https://www.cndba.cn")
    time.sleep(1)
    print("result is %d" %(a+b))

@deco
def func2(a,b,c):
    print("中国DBA社区网址:https://www.cndba.cn")
    time.sleep(1)
    print("result is %d" %(a+b+c))


if __name__ == '__main__':
    f = func
    func2(3,4,5)
    f(3,4)
    #func()

6 带参数的装饰器

如果一个装饰器,对不同的函数有不同的装饰。那么就需要知道对哪个函数采取哪种装饰。因此,就需要装饰器带一个参数来标记一下。例如:
@decorator(parameter = value)

比如有两个函数:

def task1():
    time.sleep(2)
    print("in the task1")

def task2():
    time.sleep(2)
    print("in the task2")

task1()
task2()

要对这两个函数分别统计运行时间,但是要求统计之后输出:
the task1/task2 run time is : 2.00……

可以构造一个装饰器timer,并告诉装饰器哪个是task1,哪个是task2:

@timer(parameter='task1') #
def task1():
    time.sleep(2)
    print("in the task1")

@timer(parameter='task2') #
def task2():
    time.sleep(2)
    print("in the task2")

task1()
task2()

再加一层函数来接受参数:

def timer(parameter): #
    print("in the auth :", parameter)

    def outer_deco(func): #
        print("in the outer_wrapper:", parameter)
        def deco(*args, **kwargs):
        return deco
    return outer_deco

timer(parameter)接收参数parameter=’task1/2’,而@timer(parameter)带括号,会执行这个函数, 相当于:
timer = timer(parameter)
task1 = timer(task1)

后面的运行就和一般的装饰器一样了:http://www.cndba.cn/cndba/dave/article/3090

http://www.cndba.cn/cndba/dave/article/3090

# -*- coding: utf-8 -*-
import time

def timer(parameter):

    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            if parameter == 'task1':
                start = time.time()
                func(*args, **kwargs)
                stop = time.time()
                print("the task1 run time is :", stop - start)
            elif parameter == 'task2':
                start = time.time()
                func(*args, **kwargs)
                stop = time.time()
                print("the task2 run time is :", stop - start)
        return wrapper
    return outer_wrapper

@timer(parameter='task1')
def task1():
    time.sleep(2)
    print("in the task1")

@timer(parameter='task2')
def task2():
    time.sleep(2)
    print("in the task2")

task1()
task2()

7 同时有多个装饰器

如果有多个装饰器,那么它们执行的顺序是从最后一个装饰器开始,执行到第一个装饰器,再执行函数本身,函数执行也有顺序,可以参考以下代码执行结果:

# -*- coding: utf-8 -*-
import time

def dec1(func):
    print("1111")
    def one():
        print("2222")
        func()
        print("3333")
    return one

def dec2(func):
    print("aaaa")
    def two():
        print("bbbb")
        func()
        print("cccc")
    return two

@dec1
@dec2
def test():
    print("中国DBA社区网址:https://www.cndba.cn")

test()

C:/Python27/python.exe D:/Dave/Study/Python/PythonProject/Practice/deco.py
aaaa
1111
2222
bbbb
中国DBA社区网址:https://www.cndba.cn
cccc
3333

版权声明:本文为博主原创文章,未经博主允许不得转载。

用户评论
* 以下用户言论只代表其个人观点,不代表CNDBA社区的观点或立场
dave

dave

关注

人的一生应该是这样度过的:当他回首往事的时候,他不会因为虚度年华而悔恨,也不会因为碌碌无为而羞耻;这样,在临死的时候,他就能够说:“我的整个生命和全部精力,都已经献给世界上最壮丽的事业....."

  • 2262
    原创
  • 3
    翻译
  • 578
    转载
  • 192
    评论
  • 访问:8072340次
  • 积分:4349
  • 等级:核心会员
  • 排名:第1名
精华文章
    最新问题
    查看更多+
    热门文章
      热门用户
      推荐用户
        Copyright © 2016 All Rights Reserved. Powered by CNDBA · 皖ICP备2022006297号-1·

        QQ交流群

        注册联系QQ