最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

Python教程之函数装饰器实例操作

XAMPP案例 admin 542浏览 0评论

写在前面:除了电影里,没人会等你四五年,说白了,感情就是不联系就没有的东西,空空如也,走马观花,贪得无厌。

 

介绍函数装饰器之前,先实现个小需求:对两个函数的运行时间进行统计。

最一般的方法就是引入时间模块,然后定义俩函数,在调用函数之前先记录一个时间节点,然后调用函数之后再计算时间差,并输出这个差值,如下:

import time


def func1(name1):
    print(name1)
    time.sleep(0.3)


def func2(name2):
    print(name2)
    time.sleep(0.5)


# 低配版:在调用之前记录开始时间,调用结束用当前时间减去开始时间
before_time = time.time()
func1("func1")
print("func1 运行时间为: {}".format(time.time()-before_time))

before_time = time.time()
func2("func2")
print("func2 运行时间为: {}".format(time.time()-before_time))

运行上面这段代码,执行结果:

dy4Python

使用这种低配方式确实可以满足我们的需求,但是如果一个很大的项目有N多个地方都需要进行时间统计,咋办?复制粘贴?那如果时间统计的需求有变化了,需要批量增加指定时长,咋办?逐条更改么?万一漏了或者改错了,咋办?

嗯…所以就需要用到之前讲过的一个偷懒的必备技能——将重复代码塞到函数中~ 传送门 -> 函数1 – 基础

使用函数优化后的部分:

def run_time(f, *args):
    before_time = time.time()
    f(*args)
    print("{} 运行时间为: {}".format(f.__name__, time.time() - before_time))

# 普通版:通过函数处理时间的统计
run_time(func1, "func1")
run_time(func2, "func2")

说明:低配版中重复的逻辑为“记录初始时间 – 调用函数 – 输出时间差”,所以就可以考虑把这一部分代码封装到函数里面。由于要调用的函数和函数的形参不是固定的,因此可以考虑使用参数进行传递赋值。偷懒函数里面的第一个形参就是函数名字,第二个形参是非关键字可变长的元组参数 (温故而知新:函数2 – 传递与变长参数)。定义完这个偷懒函数后,在需要的时候,将要统计运行时间的函数名字和所需参数以实参形式传递给偷懒函数就可以啦~

dy04Python

这样的方式确实可以解决代码冗余的问题,但是改变了函数的调用方式,所以看起来也不是一个好办法。接下来我们尝试使用闭包通过保存外部变量状态对函数进行改造(呀…忘了闭包还没有介绍过呢):

补充:在函数体内创建的函数叫做内部函数,创建内部函数的方法就是在外部函数体内使用def关键字定义一个新的函数。在内部函数中,对外部函数内(是外部作用域,而不是全局作用域)的变量进行引用,这个内部函数就被认为是闭包(闭包也是函数,上面这段代码中inner函数就是个闭包)。定义在外部函数内、但由内部函数引用或使用的变量,被称为自由变量(代码中f就是自由变量)。闭包将内部函数自己的代码和作用域与外部函数相结合(简单来说就是闭包会携带一些额外的作用域),因此闭包变量存活在一个函数的名称空间和作用域内(名称空间和作用域后面会详细介绍)。
def run_time(f):
    def inner(*args):
        before_time = time.time()
        f(*args)
        print("{} 运行时间为: {}".format(f.__name__, time.time() - before_time))
    return inner


# 闭包版:使用外部函数变量
run_time(func1)("func1")
run_time(func2)("func2")
说明:外部函数run_time只需要接收一个函数名称的参数,并且返回了一个内部函数inner,在外部函数内定义了这个内部函数,内部函数接收的是传入函数所需的参数,因为不知道要统计时间的函数具体有多少个参数,所以这里面使用非关键字可变长参数作为形参(也可以加上关键字变长参数进行优化)。在内部函数中先记录当前时间,然后调用被统计的函数f,最后打印出f运行所需要的时间。
dy004Python

运行这段代码,执行顺序为:先导入time模块,接着定义func1、func2、run_time三个函数,然后调用run_time函数,实参为func1,func1就传给函数定义中的形参f(f=func1),在run_time函数中依然是先走定义,定义函数inner,然后返回inner。第23行前半部分相当于执行完毕,此时run_time(func1)等价于inner。所以第23行的代码可以理解为变成了inner(“func1”),即:对inner函数进行调用,实参”func1″传递给函数定义中的形参*args(元组参数中只有”func1″一个元素)。

接下来继续执行内部函数要干的事情,也就是记录当前时间,调用func1函数,形参name1=”func1″(f(*args)等价于func1(“func1”)),此时会去执行func1函数要干的事情,也就是输出传递过来的字符串”func1″并且延迟等待0.3s,0.3s过后函数func1执行完毕回到第17行,然后继续执行下面的代码,也就是输出函数运行用了多少时间。

上面这个版本其实就是今天我们要说的装饰器函数,在不改变被装饰函数调用方式和内部代码的情况下,给被装饰函数增加额外的功能。后面哪个函数需要统计时间,就可以在被装饰函数定义后面调用这个装饰器run_time函数,把被装饰函数重新绑定到run_time函数的返回对象中即可。

由于Python提供了装饰器修饰符“@”符号,所以可以直接在函数定义前使用@run_time来对指定函数进行装饰,Python就会自动帮我们完成手工赋值的操作:

import time


def run_time(f):
    def inner(*args):
        before_time = time.time()
        f(*args)
        print("{} 运行时间为: {}".format(f.__name__, time.time() - before_time))
    return inner


@run_time
def func1(name1):
    print(name1)
    time.sleep(0.3)


def func2(name2):
    print(name2)
    time.sleep(0.5)


# 高配版:使用了装饰器
func1("func1")
run_time(func2)("func2")

第24行和第25行的代码是等价的,只是我们在func1函数定义前增加了函数装饰器,所以我们只需要直接调用func1,也可以完成run_time函数中要做的事情。它的执行顺序也是先进行三个函数的定义,然后在调用函数func1的时候,发现上面有个装饰器,就会先去执行装饰器里面的代码,其他逻辑和上面的“闭包版”一样~

dy0004Python

总结:装饰器实际上就是函数,它可以让其他函数在不做任何代码改动的情况下增加额外的功能,装饰器的返回值也是一个函数对象。

注意

1. 装饰器是在函数调用之上的修饰
2. 这些装饰只有在声明一个函数或方法的时候,才会被应用的额外调用
3. 装饰器的语法以@开头,然后是装饰器函数的名字和可选的参数,后面紧跟着的是被修饰的函数和可选参数。
语法如下:
@decorator(dec_opt_args)
def funcdecorated(func_opt_args):
    pass

上面我们举的统计函数运行时间的例子,就是个没有参数的装饰器,没有参数的装饰器简化来说就是:

@deco
def func():
    pass

等价于:

func = deco(func)

有参数的装饰器(即:需要自己返回以函数作为参数的装饰器):

@deco(deco_arg)
def func():
    pass

说明:deco()用deco_arg做了些事情并返回函数对象,而该函数对象是以func作为其参数的装饰器,等价于:

func = deco(deco_arg)(func)

当然,我们也可以对一个函数使用多个装饰器,装饰从最靠近函数定义的地方开始。但是装饰器执行的顺序是:最靠近函数定义的地方最后执行。

语法:

@deco2
@deco1
def func(arg1, arg2, ...):
    pass

等价于:

def func(arg1, arg2, ...):
    pass
func = deco2(deco1(func))

举个小例子:

def run_time1(f):
    def inner(*args):
        print("装饰器1开始了")
        f(*args)
        print("装饰器1结束了")
    return inner


def run_time2(f):
    def inner(*args):
        print("装饰器2开始了")
        f(*args)
        print("装饰器2结束了")
    return inner


@run_time1
@run_time2
def func(name1):
    print(name1)


# 高配版:多个装饰器
func("functest")

说明:run_time2会先对原始函数进行装饰,然后是run_time1对run_time2装饰之后返回的结果进行装饰,等价于run_time1(run_time2(func))(“functest”)。

※ 执行顺序:

首先定义三个函数,然后在第24行执行func函数的调用,由于func函数从上到下依次被run_time1和run_time2装饰过,所以最先进入run_time1函数内(此时实参为run_time2(func),即:f=run_time2(func))。

在run_time1中先定义了一个内部函数inner,然后返回了inner,代码run_time1(run_time2(func))(“functest”)相当于inner(“functest”),也就是对inner函数的调用,所以它会进入inner函数中输出“装饰器1开始了”;

然后准备调用函数,这里面的f(*args)等价于run_time2(func)(“functest”),这里面涉及到run_time2函数的调用,在run_time2中,也是先定义了inner函数,然后返回了inner,代码run_time2(func)(“functest”)相当于inner(“functest”),也就是对inner进行调用,所以继续执行run_time2中内部函数inner中的代码,因此会输出“装饰器2开始了”;

然后执行第12行的f(*args),此时f=func,*args元组参数中只有一个元素“functest”,所以f(*args)等价于func(“functest”),调用了func函数,输出形参“functest”,函数执行完毕,然后继续执行下面的代码,也就是输出“装饰器2结束了”,此时run_time1中的f(*args)执行完毕,继续执行第5行代码,输出“装饰器1结束了”,函数结束。

dy00004Python

多个装饰器也可以有自己的参数:

@deco1(deco_arg)
@deco2
def func():
    pass

等价于:

func = deco1(deco_arg)(deco2(func))

现在大家明白函数装饰器是咋回事了嘛?

之所以要使用装饰器是因为它可以让我们的代码更加简洁,还可以增强函数的功能,而且还能提高程序的可复用性和可读性。

 

我们通常会用装饰器来做这些事情:

1. 引入日志
2. 增加计时逻辑来检测性能
3. 给函数加入事务的能力

好啦,还有不太清楚的地方可以去网上查资料,也可以直接问我哦~

转载请注明:XAMPP中文组官网 » Python教程之函数装饰器实例操作

您必须 登录 才能发表评论!