python基础知识学习——装饰器

** 装饰器**

1.装饰器的概念

  • 装饰器的本质就是一个函数,它的作用是为其他函数添加一个新的功能,但是不改变原函数的源代码和调用方式。
  • 装饰器的两大原则:
    1. 不修改被修饰函数的源代码
    2. 不修改被修饰函数的调用方式

2.装饰器的知识储备(或者我们可以理解成,一个装饰器是由什么组成)

  • 装饰器 = 高阶函数+函数嵌套+闭包

3.装饰器的实现

  1. 首先我们定义一个累加求和的函数
import time
def cal():
    res = 0
    for i in range(100):
        res+=i
    time.sleep(1)
    print("函数的运行结果为",res)
    return res
cal()

在这里插入图片描述

  • 这是我们的一个初始函数,如果此时我们需要为这个函数添加一个功能,例如我们需要计算这个函数需要的时间。想想好像也容易做到这一点的吧。通过调用函数前和函数运行结束后分别记下时间戳,那么我们就成功的为这个函数记下了运行的时间。
import time
def cal():
    start_time = time.time()
    res = 0
    for i in range(100):
        res += i
    time.sleep(1)
    stop_time = time.time()
    print("函数的运行结果为", res)
    print("函数的运行时间为",stop_time-start_time)
    return res
cal()

这是修改函数运行后得到的结果

  • 通过上面的这种方法,我们很容易就达到了我们需要做得目的了,也就是说我们成功的为函数添加了一个新的功能——记录函数运行的时间。不过,这只是其中的一个函数,如果说有成百上千个函数,我们也要这样一个个的添加功能吗?这显然不太现实。
  • 更关键的一点,我们这种做法已经违反了开放封闭原则。开放封闭原则简单的理解就是我们写的程序上线以后,我们就不能对程序的源代码经行修改。如果函数在其他位置被调用了的话,我们便不知道会不会出现其他的后果,有可能引起一系列的连锁反应。所以说,上面的方法,我们是行不通的。
  • 装饰器的第二个原则:不修改被修饰函数的调用方式,我可以为函数添加新的功能,但是不能改变它的调用方式,在上面的函数中,我们通过cal()进行调用函数,那么我们添加了功能之后,我们就必须按照原来的方法,用cal()来调用函数。
  1. 高阶函数的使用
    • 首先我们说一下,什么是高阶函数?
      满足下面两个条件中的其中一个都可以成为高阶函数:
      • 函数接受的参数是一个函数名
      • 函数的返回值是一个变量名
    • 我们继续拿上面的函数做例子
import time
def cal():
    res = 0
    for i in range(100):
        res+=i
    time.sleep(1)
    print("函数的运行结果为",res)
    return res

def test(func):
    print(func)
    func()
test(cal)

那么这里的test就是一个高阶函数。因为它接收的参数是一个函数名func,看到这里,我们就可以进行对函数cal添加新的功能了,继续以记录函数运行时间作为一个例子。

import time
def cal():
    res = 0
    for i in range(100):
        res+=i
    time.sleep(1)
    print("函数的运行结果为",res)
    return res

def test(func):
    print(func)
    start_time = time.time()
    func()
    stop_time =  time.time()
    print("函数运行的时间为",stop_time-start_time )

test(cal)

来看下这个函数运行的结果:
python基础知识学习——装饰器_第1张图片
那这时候,我们不但没有修改原函数的源代码,而且还为函数添加了一个新的功能,我们的功能就实现了,装饰器就这么结束了。难道真的结束了吗?你注意到了没,函数的方式已经发生改变了,初始我们调用函数使用cal(),而现在却是test(cal),这就违反了我们上述所说的装饰器的第二原则。
那么我们来看下高阶函数第二种定义:返回值是一个函数名

import time
def cal():
    res = 0
    for i in range(100):
        res+=i
    time.sleep(1)
    print("函数的运行结果为",res)
    return res
    
def test(func):
    start_time = time.time()
    cal()
    stop_time = time.time()
    print("函数的运行时间为",stop_time-start_time)
    return func
    
cal = test(cal)
# print(res)
cal()

通过传入参数,能够让我们避免对源代码的修改,但修改了函数的调用方式
通过返回值为函数名,能够让我们避免修改函数的调用方式
这时我们通过对添加功能函数的参数传入为函数,返回值为函数名,此时,我们满足了装饰器的两个原则。我们这种貌似是可取的,其实不然。我们可以发现在添加功能时,需要对函数执行一次才可以,这不符合我们的要求。
因此,仅仅只有高阶函数并不能满足于对我们的需求,不知道还是否记得开头写的对装饰器的知识储备。装饰器=高阶函数+函数嵌套+闭包。接下来,我们来了解什么时闭包吧。

4.闭包

  1. 闭包的理解:有很多小伙伴都不懂什么是什么,比较模糊,其实我们可以理解闭包为一种特殊的嵌套函数,这个函数由两个函数嵌套组成,那么在结构上就有外函数和内函数的说法。外函数返回值是内函数的引用结果
  • (1)外层函数out_func可以调用内层函数in_func,但无法引用in_func内部的变量y

  • (2)内层函数in_func可以引用外层函数out_func的变量x

def out_func():
	x = 1
    def in_func(y):
        return x+y
    return in_func
test = out_func()
print(test(10))
  • test是闭包函数,自由变量是x,因为它不是in_func这个函数内部的变量,但是却被in_func引用。test的结果是函数in_func的地址,那么通过test()便可调用函数in_test.
  • 每一层的函数,我们可以成为一个包,而闭就是封装的意思,它封装的是变量,嵌套在函数内的函数等价于一个变量,遵循函数即变量的原则。其实闭包有点类似于作用域,若最里层需要一个变量,我们就可以在当前层定义,如果当前层不允许,则往上一层,一层一层的往外,也就是说我们可以在最外层定义一个变量,那就可以渗透到最里层
  • 闭包的最大用处也是用于装饰器,接下来,让我们看看装饰器的作用吧。

5.装饰器的实现

  • 我们继续以上面的例子来了解装饰器,我们要为一个函数计算运行时间。
    来,先看一段代码吧。
import time
def timer(func):
    def wrapper():
        # print(func)
        start_time = time.time()
        func()
        stop_time = time.time()
        print(stop_time-start_time)
    return wrapper
#@timer  #等价于 cal = timer(cal)
def cal():
    res = 0
    for i in range(100):
        res+=i
    time.sleep(1)
    print("函数的运行结果为",res)
    return res

cal = timer(cal)
cal()

事到如此,我们基本 完成了装饰器的功能了,我们不但没有修改函数的源代码,也没有修改其调用方式,还为它添加了一个新的功能。
不过,这样子还存在这一丢丢瑕疵,因为每次给函数添加功能是都需要做一次赋值操作,那能不能不每次调用都赋值一次? 那就要用到了一个 @,这是python提供的一个功能。
@timer 相当于 cal = timer(cal),我们要修饰那个函数,就在那个函数前添加@timer

6.含返回值的装饰器

  • 上面的样子看上去似乎已经满足了我们的要求了,但好像还有点小问题额。。。如果我们需要用到cal函数的返回结果的时候,这貌似不符合我们的心意啊。
res = cal()
print("这是函数的返回结果的cal:",res)

输出的结果竟然是None?????
python基础知识学习——装饰器_第2张图片

  • 仔细研究一下,cal经过装饰后,本质上就是上面的经过升级后的wrapper函数,而这个函数是没有返回值的,因此,cal()运行的结果返回就是默认的None啦。
    def wrapper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print(stop_time-start_time)
  • 来看看这一段wrapper函数,我们需要func的返回值,那么就需要在wrapper函数加上return返回咯,返回的值是func的结果,那就简单了,吧func返回的结果赋值给变量res,然后return返回res这样就解决啦。
import time
def timer(func):
    def wrapper():
        # print(func)
        start_time = time.time()
        res = func()
        stop_time = time.time()
        print(stop_time-start_time)
        return res
    return wrapper
@timer 
def cal():
    res = 0
    for i in range(100):
        res+=i
    time.sleep(1)
    print("函数的运行结果为",res)
    return res

res = cal()
print("这是函数的返回结果的cal:",res)

7.含参数的装饰器

上面的例子,我们的cal函数都是不带参数的,但是,我们并不能保证以后我们所写的每一个函数都是不带参数的。

  • 举个例子吧,我们在原来的基础上定义了一个test1函数:
def timer(func):
    def wrapper():
        # print(func)
        start_time = time.time()
        res = func()
        stop_time = time.time()
        return res
    return wrapper
    
@timer
def cal():
    res = 0
    for i in range(100):
        res+=i
    time.sleep(1)
    print("函数的运行结果为",res)
    return res
@timer
def test1(name):
    time.sleep(1)
    print("这是%s"%(name))
    return res

我们在不修改装饰器的情况下,就会报错了,wrapped函数不需要函数,但是一个给出了。
在这里插入图片描述
那么我们就要在wrapped函数中加上参数name,不过,依然不能解决问题。
在这里插入图片描述
这是我们就需要用到了可变参数了。 如果我们不确定要往函数中传入多少个参数,或者我们想往函数中以列表和元组的形式传参数时,那就使要用*args; 如果我们不知道要往函数中传入多少个关键词参数,或者想传入字典的值作为关键词参数时,那就要使用**kwargs。

def timer(func):
    def wrapper(*args,**kwargs):
        # print(func)
        start_time = time.time()
        res = func(*args,**kwargs)
        stop_time = time.time()
        return res
    return wrapper

这里我们对wrapper函数进行修改,在wrapper中就如位置可变参数和关键字可变参数,这样的话,我们就不用再担心被修饰函数有多少个参数,以及我们定义的装饰器可以修饰任何函数啦。

好啦,装饰器就到此结束了。。。。。。

你可能感兴趣的:(python全栈学习)