博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python学习之装饰器
阅读量:2722 次
发布时间:2019-05-13

本文共 5868 字,大约阅读时间需要 19 分钟。

第一部分:装饰器定义

装饰器本身也是一个函数,装饰器是用来修饰函数的,可以简单的理解为装饰器是对一个函数稍加改造而实现某些特定的功能

第二部分:知识储备

函数即变量

首先看一下几个定义的简单函数及后续操作

重点看函数定义的位置有什么不同!
1.
代码

def Alice():   print("this is Alice")def Bob():    print("this is bob")    Alice()Bob()

执行结果

this is bobthis is Alice

分析:Alice定义在Bob的上边,Bob在定义中引用了Alice,按解释器的解释规则来看,代码是自上而下一行一行执行的,这样的执行过程完全没有问题

代码

def Bob():   print("this is bob")   Alice()def Alice():   print("this is Alice")Bob()

执行结果

this is bobthis is Alice

分析:这里我么将Alice定义在了Bob的下边,此时有些人会困惑了,程序是一行一行执行的,那么在执行到def Bob()这段语句中,里面调用的Alice是没有定义的,为什么没有报错呢?这就是我想让大家get到的一点!

经过以上两个例子,大家应该已经发现了这其中的一些问题,现在我们对上述出现的情况进行解释

(1)首先我们来分析一下函数的定义结构

def Alice():                   ###定义函数名   print("this is Alice")      ###定义函数体

函数的整个定义结构,分为函数名和函数体

(2)打印下面的这两种情况

def Alice():   print("this is Alice")print(Alice)print(Alice())

执行结果

###是一个内存地址this is Alice ###执行Alice()产生的输出None ###此处是Alice()这个函数的返回值,因为没有设置,所以默认是空

分析

第一个print输出的是一个内存地址,这个地址就是Alice()这个函数存放的地址,里面存放的就是函数定义中的函数体。
举一个简单的例子
我们定义一个x=10,x是一个变量名,10是一个数值,定义的过程就是,为x这个变量在内存中开辟一个空间,然后将10这个数值存入到里面
函数的定义也是类似的,def Alice()就是先在内存中申请一块空间(这块空间在我们看来可以叫做Alice),然后将函数体(实际执行的部分)放入到里面。当调用函数时,其实就是将函数中的函数体中的语句都执行一遍!

这样就解释了上边为什么第二种情况是可行的,因为函数的定义语句(def Alice())只是将函数体存在了指定的空间中,而并没有实际的去运行它。(可以通过设置断点的方法来一步一步的看程序的执行过程)。这样就简单的说明了什么是函数即变量了!

函数嵌套

函数嵌套的定义:在一个函数中再定义一个函数,再定义的这个函数只在父函数内有意义,即作用域是有限的,只在父函数内有效

下面看一个函数嵌套的简单例子

def Alice():   print("this is Alice")   def Truddy():      print("this is Truddy")   Truddy()Alice()
this is Alicethis is Truddy

高阶函数

判断是否是高阶函数,有两个标准

1.如果传入函数A的参数是一个函数,那么函数A是高阶函数
2.如果函数A的返回值是一个函数,那么函数A是高阶函数
下面看两个例子
例子一,函数的参数是一个函数

def Bob():   print("this is Bob")def Alice(func):   print("this is Alice")   print("-------------")   func()Alice(Bob)   ###这里传入的是函数的内存空间,如果按这种写法Alice(Bob())会出现一些问题,有兴趣的可以自己试一下,这里就不再解释了
this is Alice-------------this is Bob

例子二,函数的返回值是一个函数

def Bob():   print("this is Bob")def Alice(func):   print("this is Alice")   print("-------------")   return funcTruddy = Alice(Bob)       ##Alice(Bob)先执行,然后返回Alice的返回值,我们定义的返回值是传入的函数print("*************")Truddy()
this is Alice-------------*************this is Bob

第三部分:装饰器

一.根据装饰器的功能对装饰器的一些要求

装饰器的功能就是在不改变原函数的源代码和调用方式的情况下,来实现对原函数的功能的增强
两点重要的要求:
(1)不改变原函数的代码
(2)不改变原函数的调用方式
为了满足这两个要求,我们就要用到前面我们所讲的函数嵌套和高阶函数了!
二.不改变原函数的代码的实现
假设我们为一个输出函数增添一个统计函数完成时间的功能,我们可以这么写

import timedef Bob():   print("this is Bob")def Alice(func):   start_time=time.time()   func()   time.sleep(3)   end_time = time.time()   print("the cost of time is {}".format(end_time-start_time))Alice(Bob)

执行结果如下

this is Bobthe cost of time is 3.0007195472717285

我们完成了Bob这个函数的输出功能,同时增添了统计时间的功能,也没有修改Bob函数的源代码,但是Bob函数的调用方式却改变了,原本只需要Bob()即可,但现在还要将Bob传入Alice(),写作Alice(Bob)。

接下来我们再看一下不改变函数调用方式的实现

三.不改变函数的调用方式的实现

import timedef Bob():   print("this is Bob")def Alice(func):   start_time=time.time()   return func   time.sleep(3)   end_time = time.time()   print("the cost of time is {}".format(end_time-start_time))Bob=Alice(Bob)Bob()
this is Bob

我们通过函数返回的方式,满足了不改变函数的调用方式,但是问题是也没有给函数增加功能

其实我们离目标已经很接近了!让我们再加上函数嵌套看看效果会怎么样!

四.使用高阶函数和函数嵌套来实现装饰器

import timedef Bob():   print("this is Bob")def Alice(func):   def wrapper():    start_time=time.time()    func()    time.sleep(3)    end_time = time.time()    print("the cost of time is {}".format(end_time-start_time))   return  wrapperBob=Alice(Bob)Bob()
this is Bobthe cost of time is 3.000948190689087

上述例子中,我们使用高阶函数解决了不修改源代码的就增添功能这一问题,又使用函数嵌套解决了不改变函数的调用就可以实现的这一问题

所以可以认为装饰器的实现实际上就是高阶函数和嵌套函数组合成的一个起修饰作用的函数

但是,如上每次函数的执行都需要进行Bob=Alice(Bob) 的转换,有一定的繁琐,我们可以用以下的方式来实现简化

import timedef Alice(func):   def wrapper():    start_time=time.time()    func()    time.sleep(3)    end_time = time.time()    print("the cost of time is {}".format(end_time-start_time))   return  wrapper@Alice              ############添加的部分在这 本质是在使用Bob()之前先执行命令 Bob=Alice(Bob)def Bob():   print("this is Bob")Bob()

我们可以在我们要被装饰的函数之上,写上@+装饰器名称

需要注意的是,@Alice实际上是执行了一次指令,这样就需要Bob的定义放在Alice的下面了,不然会有找不到Alice的报错

五.装饰器的进阶

以上我们举得例子中函数都是没有参数的,那么如果我们需要往函数中传参数的时候该怎么办呢
以下给出操作

第一代

import timedef Name_time(func):   def wrapper():    start_time=time.time()    func()    time.sleep(3)    end_time = time.time()    print("the cost of time is {}".format(end_time-start_time))   return  wrapper@Name_timedef Name(name):   print("my name is {}".format(name))Name("Bob")

执行结果

Traceback (most recent call last):  File "C:/Users/fuxiangyu/PycharmProjects/fuxiangyu/基础实验/标准输出.py", line 15, in 
Name("Bob")TypeError: wrapper() takes 0 positional arguments but 1 was givenProcess finished with exit code 1

执行之后,发现报错,原因是我们并没有定义往Name_time()中的wrapper()传参数,但是缺传入了一个参数(关于为什么时wrapper报错,是因为此时Name()是wrapper函数的返回,可以理解为现在的Name是指向wrapper()的内存空间的)

第二代

import timedef Name_time(func):   def wrapper(name):    start_time=time.time()    func(name)    time.sleep(3)    end_time = time.time()    print("the cost of time is {}".format(end_time-start_time))   return  wrapper@Name_timedef Name(name):   print("my name is {}".format(name))Name("Bob")

执行结果

my name is Bobthe cost of time is 3.000530242919922

我们在wrapper()函数的定义中加入了参数,使得函数可以正确的运行了,但是有个问题,就是如果我想拿这个装饰器也去修改别的函数,别的函数可能有参数,可能没有参数,可能有一个或者多个函数的情况下该怎么做呢?

接下来我们再来对其功能进行加强

第三代

import timedef timer(func):   def wrapper(*arr,**kwargs):    start_time=time.time()    func(*arr,**kwargs)    time.sleep(3)    end_time = time.time()    print("the cost of time is {}".format(end_time-start_time))   return  wrapper@timerdef Pure_out():   print("Python is the best language")@timerdef Name(name):   print("my name is {}".format(name))@timerdef Name_and_Age(name,age):   print("my name is {} ,my age is {}".format(name,age))Pure_out()Name("boy")Name_and_Age("Bob","21")

执行结果

Python is the best languagethe cost of time is 3.000469446182251my name is boythe cost of time is 3.00105357170105my name is Bob ,my age is 21the cost of time is 3.0003440380096436

通过向wrapper函数传入(*arr和**kwargs)来实现,可以接收任何形式的输入

至此,装饰器的基本介绍到此为止!

转载地址:http://wwttd.baihongyu.com/

你可能感兴趣的文章
C++11 模板改进
查看>>
C++11 基于范围的for循环
查看>>
Windows进程线程相关概念
查看>>
Windows创建进程
查看>>
Windows进程管理
查看>>
Windows进程通信——匿名管道
查看>>
MFC只运行一个实例窗口
查看>>
C++STL 仿函数
查看>>
Windows线程管理和调度机制概述
查看>>
Windows线程同步——临界区对象
查看>>
Leetcode——15. 3Sum
查看>>
Leetcode——18.4Sum
查看>>
Windows线程同步——互斥量对象
查看>>
CRC校验原理
查看>>
UDP:用户数据报协议
查看>>
TCP:传输控制协议
查看>>
TCP同时打开和同时关闭
查看>>
TCP连接建立与结束(三次握手与4次挥手)
查看>>
TCP 窗口协议
查看>>
TCP 的超时与重传
查看>>