python装饰器

旧巷老猫 提交于 2020-02-17 07:36:27

一 什么是装饰器?

简单地可以把装饰器理解为给函数增加扩展功能的函数,也就是说相当于在函数的运行过程中做些处理,且不影响函数原来的功能。

二 装饰器有什么用?

我们写了一堆函数,现在需要增加一项扩展功能,比如,在运行函数前,对函数进行日志统计,这样我们就可以写一个装饰器来解决问题。

也许大家会疑问,我们为什么不在原来函数中进行修改,第一,既然是扩展功能,我们可能不希望扩展功能影响原来的函数,通过装饰器,我们就没有必要将扩展工能的代码写入原来的函数代码里面,第二,装饰器也可以实现代码重用的目的。下面,我们将一步步创建一个python的装饰器。

三 一步步创建一个装饰器

1. 先介绍下python的函数对象

python的函数是一个对象,可以赋值给变量,所以通过变量也能调用该函数

In [1]: def hello():
   ...:     print("hello,world")
   ...:

In [2]: f = hello

In [3]: f()
hello,world

函数对象也有__name__等一些属性

In [4]: f.__name__
Out[4]: 'hello'

In [5]: hello.__name__
Out[5]: 'hello'

 

2. 我们就以给函数增加一个统计函数运行时间的功能为例子创建一个装饰器

创建一个简单的函数myfunc

import time

def myfunc():
    time.sleep(1)

 给这个函数,增加一个扩展功能,我们需要创建另一个函数deco

import time

def deco(func):
    start = time.time()
    func()
    end = time.time()
    print("duration:{}".format(end-start))

def myfunc():
    time.sleep(1)

这样,我们就可以调用deco函数来给myfunc函数增加扩展功能

import time

def deco(func):
    start = time.time()
    func()
    end = time.time()
    print("duration:{}".format(end-start))

def myfunc():
    time.sleep(1)
# 原函数调用myfunc()# 装饰后的函数调用
deco(myfunc)运行文件后,输出为duration:1.00099992752

 这样有一个问题,所有调用myfunc()的地方都要改为deso(myfunc),为了解决这个问题,我们可以对代码进行一些改动,不必影响原来函数的调用

import time

def deco(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print("duration:{}".format(end-start))
    return wrapper

def myfunc():
    time.sleep(1)

# 进行改动后的调用
myfunc=deco(myfunc)
myfunc()
运行文件后,输出为duration:1.00099992752

这次的改动,让deco函数的返回对象为一个函数wrapper,我们将返回的对象赋值给原来的函数名,再通过调用原来的函数来运行返回的函数,这样一个完整的装饰器就实现了,每次调用时,都需要对myfunc进行处理 myfunc=deco(myfunc)。在python里面有个装饰器的语法糖“@”可以来精简代码

import time

def deco(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print("duration:{}".format(end-start))
    return wrapper

# 语法糖
@deco
def myfunc():
    time.sleep(1)

myfunc()
运行文件后,输出为duration:1.00099992752

使用了”@”语法糖后,我们就不需要额外代码来给”myfunc”重新赋值了,其实”@deco”的本质就是”myfunc = deco(myfunc)”

前面是原函数没有带参数的装饰器,原函数带参数的装饰器,只需要将参数传入wrapper函数中,下面是一个简单的例子

import time

def deco(func):
    def wrapper(a,b):
        start = time.time()
        func(a,b)
        end = time.time()
        print("duration:{}".format(end-start))
    return wrapper

# 语法糖
@deco
def myfunc(a,b):
    time.sleep(1)
    print(a+b)

myfunc(1,2)运行文件后,输出为:3duration:1.00099992752

基本上,我们的装饰器已经大功告成了,但还有一个小小的问题,python中,函数也是对象,有很多属性,比如__name__等,让我们先看下被装饰器改动后的函数的一些属性

import time

def deco(func):
    def wrapper(a,b):
        start = time.time()
        func(a,b)
        end = time.time()
        print("duration:{}".format(end-start))
    return wrapper

@deco
def myfunc(a,b):
    time.sleep(1)
    print(a+b)

print(myfunc.__name__)运行文件后,输出为wrapper

我们发现函数的名字发生了变化,因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,这样,就能保持原来函数的属性,否则,有些依赖函数签名的代码执行就会出错。Python内置的functools.wraps就是完成这件工作的,一个完整的decorator的写法如下:

import time
import functools

def deco(func):
    @functools.wraps(func)
    def wrapper(a,b):
        start = time.time()
        func(a,b)
        end = time.time()
        print("duration:{}".format(end-start))
    return wrapper

@deco
def myfunc(a,b):
    time.sleep(1)
    print(a+b)

print(myfunc.__name__)
运行文件后,输出为myfunc

四 小结

python的装饰器,定义起来比较复杂,但使用起来非常灵活方便,当我们想给函数增加一个功能,或者在函数运行前或者运行后进行一些处理,而不想影响原来函数的代码逻辑,我们就可以灵活运用装饰器来实现。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!