Python之装饰器

 

        在学习Python的过程中,我对装饰器一直觉得很困惑,并通过思考和浏览一些教学视频才略有一些领悟,我希望以下总结的内容会对你有帮助,我用也用一些简单的案例演示了出来。文中语言组织较俗,如有遗漏和不足,欢迎交流和指点。 

允许转载并注明出处:https://blog.csdn.net/L_obsession/article/details/84777435

装饰器前言:  

有如下简单程序(decorator_1.py): 

def print_name():
    print("I am Mr.right!")

def welcome():
    print("Welcome to the China!")

print_name()
welcome()

#运行结果:
I am Mr.right!
Welcome to the China!

 那现在我有一个需求,要让你把程序(decorator_1.py)中函数print_name( )和welcome( )增加一个新的功能打印 "欢迎您!",那怎么去做这件事呢?没学装饰器前我们会直接在源代码两个函数中添加新代码,如下:

def print_name():
    print("I am Mr.right!")
    print("欢迎您!")
def welcome():
    print("Welcome to the China!")
    print("欢迎您!")
print_name()
welcome()

现在试想,假如你写的一个应用程序,这个应用程序包含100个函数,已经在线上运行了,某一天,你的产品经理说咱需要往这个应用程序中新增一段功能,那怎么去做呢?找出100个函数,挨个修改,增添功能?但是问题是你的程序已经正常运行了,若修改程序的源代码,就意味着有风险发生,不知道会出现什么情况,万一改了一个操作,很可能导致连锁反应,线上业务就会崩掉。那么,这么做显然不合理!所以说,要是新增一个功能,第一,显然你不能修改源代码,你的函数一旦写好了,原则上你就不能去修改。第二,你不能修改原有函数的调用方式。因此,这就有了装饰器的存在。


装饰器:

定义:本质就是函数。(用来装饰其他函数), 装饰器实际上就是为了给某程序增添附加功能,但该程序已经上线或已经被使用,那么就不能大批量的修改源代码,这样是不科学的也是不现实的,因为就产生了装饰器,使得其满足两个原则:

1. 不能修改被装饰的函数的源代码

2. 不能修改被装饰的函数的调用方式

同时满足原则1、2的情况下给程序增添功能,装饰器对被修饰的函数是“完全透明的”,什么叫“完全透明”?假如对function是一个函数,decorator作为函数function的装饰器,对function来说,根本就感受不到decorator的存在,为什么感受不到?第一点,decorator没有动函数function的源代码,没有对function做任何修改;第二点,function该怎么运行,还是怎么运行,没有被decorator修改运行方式。

下面我们先看一个小案例,现有程序demo.py:

import time

def function():
    #代表function里面运行一个逻辑花费的时间
    time.sleep(3)
    print("In the function!")
function()

#运行结果:
In the function!

那现在我要统计demo.py程序中function函数运行的时间,用装饰器实现,decorator_demo.py代码如下:

import time

def timer(func):
    def wrapper(*args,**kwargs):
        start_time = time.time()
        func()
        stop_time = time.time()
        print("The runtime is %s" %(stop_time - start_time))
    return wrapper

@timer
def function():
    #代表function里面运行一个逻辑花费的时间
    time.sleep(3)
    print("In the function!")
function()

#运行结果:
In the function!
The runtime is 3.00099778175354

#装饰器既没有改变原程序的源代码,也没有改变其调用方式,这就是装饰器的好处

 装饰器的应用:

实现装饰器知识储备:

1. 函数即“变量”

2. 高阶函数

3. 嵌套函数

高阶函数 + 嵌套函数 => 实现一个装饰器

1.函数即“变量”:

代码1.
def funciton():
    print("In the function!")
    function_2()

funciton()

#NameError: name 'function_2' is not defined
#那怎么解决这个问题呢?-->写一个function_2()

 

代码2.
def function_2():
    print("In the function_2!")
def funciton():
    print("In the function!")
    function_2()

funciton()

#运行结果:
In the function!
In the function_2!



代码3. 那如果把function_2()定义到function下面呢?

def funciton():
    print("In the function!")
    function_2()
def function_2():
    print("In the function_2!")

funciton()

#运行结果:
In the function!
In the function_2!

代码4. 那要是下面这种写法呢?
def funciton():
    print("In the function!")
    function_2()

funciton()

def function_2():
    print("In the function_2!")

#NameError: name 'function_2' is not defined

这又是为什么呢?

我们回想一下变量的定义:

我们知道变量存在于内存当中,那我们可以把内存想象称为一栋大楼。如下图,大楼里面是不是有一个个小的房间? 那现在我们定义一个变量 X = 1,它在内存中以一个怎样的形式存在的。或者还想象这个大楼的例子,我要把变量存在这个大楼里,它是怎么存的呢?是不是在这个大楼里找一个小的房间,然后把“1”这个实实在在的变量放进去,然后呢这个房间是不是有一个门牌号?那么这个门牌号是不是就是这个变量名 X ,那函数即“变量”就是这个道理。什么意思呢? 

假如现在有一个函数:  

def test():
    pass

它就相当于 test = “函数体”,就是把函数体赋值给test变量,只不过比起变量,变量调用时直接调用,而函数调用时要加上 "( )" 如test( )。所以定义一个函数就类似于定义一个变量,例如定义test函数,它就在内存中找了一个小房子,然后把函数体放到里面,这个小房子的门牌号就是函数名(test)。 

 python内存的回收机制是解释器去做的,不用我们去管,那它是怎么做的呢?解释器怎么去回收你的变量呢?它有个概念叫"引用计数",什么叫引用计数,比方说x=1,它会先在内存当中实实在在把"1"存放起来。那这时候,如果再有一个x=y,他实际上是在做什么事? 是不是又把"1"这个房间又加了个门牌号叫y。那引用计数是什么意思呢?这个x代表一次引用,y也代表一次引用,加在一起是两次引用,python什么时候会把房间"1"给清空呢?什么时候会回收呢?他会等到y这个门牌号没有了,x这个门牌号也没有了,就会把"1"给清空掉,也就是门牌号都没有的情况下,没人用了,它就给你清掉回收了。

那还有匿名函数是怎么存的呢?如匿名函数: lambda  x : x*3,它一样会在内存中找一个房间放进去,lambda定义的这个就是它的函数体,但是它没有名字,也就意味着没有门牌号,没有门牌号它就意味着会被回收。所以匿名函数可以先赋值给一个变量,

calc = lambda x:x*3, 赋值给一个变量,就意味着建立一个门牌号。变量有内存回收机制,函数同样也有,原理是一样的。没有引用,就会立马回收。

明白这些,上面的几段代码(1,2,3,4)也就可以彻底理解了吧。上面的代码2和代码3有什么区别呢?运行结果为什么会是一的,举个例子来说:

假如现在有一个x=1,y=2它们在内存中存的方式如图,写成代码的方式为:

x=1
y=2
#然后调用
print(x,y)

#运行结果:
1 2

 代码2和代码3的情况就相当于把上面的调用方式改为:

y=2
x=1
print(x,y)

#运行结果:
1 2

它们的运行结果一样。这两个方式对于调用来说没有任何区别,python是一个解释型语言,它俩在调用之前被python解释器给解释到了,只要它一解释到,内存当中就存在了,存在就能调用了。

这时候来看代码2和代码3,为什么没有区别?

函数即"变量",变量先定义再使用,函数也是这样。例如代码2中,程序一运行,python解释器开始一行行解释,把函数体print("In the function_2")放在内存当中,对应function_2这个名,这个函数就已经存在了,同理function也是,调用function时,就可以运行了。在代码3中

总结:变量在使用的时候实际上是分成两步的:

  • 定义 

  • 调用

2.高阶函数: 

满足下列条件之一就可成函数为高阶函数:

    a. 把一个函数名当做实参传给另一个函数(在不修改被装饰函数源代码的情况下为其添加功能)

    b. 函数的返回值包含n个函数,n>0(不修改函数的调用方式)

其实,这两个条件都是严格的遵循了“函数即“变量”!”

高阶函数.py:

def bar():
    print("In the bar")

print(bar)

#运行结果:
<function bar at 0x00EA9540>
#打印的是bar的内存地址,也就是上面所说的 "门牌号"。
实际上bar()就是基于<function bar at 0x00EA9540>这个内存地址在做调用
那就有了:


def bar():
    print("In the bar!")
def func(param):
    print(param)
    param()  #param就代表内存地址,加上小括号bar()就可以运行
func(bar)  #这句话其实就是做了一件事——>bar = param   既然bar = param了,那就可以bar()运行
param = bar
param()

运行结果:
<function bar at 0x0000017802182488>
In the bar!
In the bar!

这就是高阶函数!符合了高阶函数条件a,那么上面这段代码为什么要把bar穿到func这个函数中再去运行呢?直接bar()运行不行么?我们来看看这种做法的牛逼之处(装饰器的功能):

 装饰器的功能:

import time
def bar():
    time.sleep(3)
    print("In the bar!")
def func(param):
    start_time = time.time()
    param()  
    stop_time = time.time()
    print("The runtime of param is %s" %(stop_time - start_time)) 
    
bar()
运行结果:
In the bar!
#先睡3三秒再打印出  In the bar!

 

import time
def bar():
    print("In the bar!")
def func(param):
    start_time = time.time()
    param()  #就是在运行传进来的bar()
    stop_time = time.time()
    print("The runtime of param is %s" %(stop_time - start_time))
    #不是func的运行时间,是你传进来的param的运行时间
func(bar)
运行结果:
In the bar!
The runtime of param is 0.0
#说明运行时间及短

func函数是修饰器修饰bar( ),此两段代码说明了,可以在不修改源代码的情况下添加附加功能,之前运行bar是直接调用的bar( ),但是此处修改了调用方式,不能再像原来那样运行了,还是不符合装饰器原则。假设你有一个程序,程序当中你有100个这种函数,你根本就不知道你定义bar( )这个函数在哪个位置被引用到,那如果你把调用方式改掉了,那就意味着你要找到所有的调用bar( )这个函数的位置,显然不可能。在生产当中,别人已经引用你的代码了,你告诉他说,我这几个代码改了,你按照新的方式调用吧,那么这种做法显然不合理!那这种虽然不能实现装饰器的功能,但至少提供了一种思路,可以在不修改源代码情况下为源代码加上功能。

返回值中包含函数名:

def bar():
    time.sleep(3)
    print("In the bar!")

def func(param):
    print(param)  #打印出bar()函数的内存地址
    return param  #把bar()函数的返回值返回回来

print(func(bar))  #打印出返回值

#运行结果
<function bar at 0x000001BEEA852488>
<function bar at 0x000001BEEA852488>

#有了内存地址,你会想到什么?是不是有了内存地址,加上小括号()就能运行,所以修改一下如下段代码:
def bar():
    time.sleep(3)
    print("In the bar!")

def func(param):
    print(param)
    return param

bar = func(bar)  
#func(bar)有一个返回值,有返回值就可以获取到,赋值给一个变量,变量bar也就是一个内存地址
#这里要注意,如果加上小括号func(bar()),相当于把bar的返回值传给了它,就不符合高阶函数的定义了
bar()  #运行bar函数
#运行结果:
<function bar at 0x000002137D232488>
In the bar!
# 此段代码即没有修改源代码,也没有改变函数调用方式,这才真正实现了一个简单的装饰器,这就是装饰器的好处!

3.嵌套函数(在一个函数体内,用def去定义声明一个新的函数,而不是去掉用它):

def foo():
    print("In the foo")
    def bar():
        print("In the bar")

bar()

#运行结果:
NameError: name 'bar' is not defined
#这里的bar()相当于是局部变量的概念,局部变量有局部作用域。定义函数就相当于定义变量!

#应该这样调用:
def foo():
    print("In the foo")
    def bar():
        print("In the bar")

    bar()
foo()

装饰器案例:

有两个函数,func_1( )和func_2( ),都有自己的逻辑。

现在实现一个装饰器,统计两个函数的运行时间,不能修改源代码和调用方式。怎么做呢?

算法:

        先搞一个高阶函数,把你要修饰的函数传进去当参数,然后在里面返回

import time

def decorator(param):  # 定义一个高阶函数
    start_time = time.time()  # 截取开始运行时间
    param()  # 直接运行param()
    stop_time = time.time()
    print("The runtime of param is %s" % (stop_time - start_time))

def func_1():
    time.sleep(3)
    print("In the func_1")

def func_2():
    time.sleep(3)
    print("In the func_2")

decorator(func_1)
decorator(func_2)

#运行结果:
In the func_1
The runtime of param is 3.002018690109253
In the func_2
The runtime of param is 3.0004374980926514

#但这种修改了函数的调用方式,那怎么样做到不修改调用方式呢?
若不修改调用方式,则:
func_1 = decorator(func_1)
func_1()
func_2 = decorator(func_2)
func_2()

这个变量func_1是得到后面括号里的func_1的内存地址,前提是你要把函数修改成带有返回值的高阶函数,怎么修改呢?直接返回?
import time

def decorator(param):  # 定义一个高阶函数
    start_time = time.time()  # 截取开始运行时间
    return param  
    stop_time = time.time()
    print("The runtime of param is %s" % (stop_time - start_time))

def func_1():
    time.sleep(3)
    print("In the func_1")

def func_2():
    time.sleep(3)
    print("In the func_2")

func_1 = decorator(func_1)
func_1()
func_2 = decorator(func_2)
func_2()

#运行结果:
In the func_1
In the func_2

#咦!没有添加新功能!显然这么做不合适,为什么会这样呢?return param后,下面的代码不会执行了。
高阶函数 + 嵌套函数 => 实现一个装饰器,此处只用了高阶函数,没有用到嵌套函数,那么把嵌套函数引入进来就可以解决上面的问题了
import time

#装饰器timer
def timer(param):  #timer(func_1),func_1传进来,param = func_1
    def decorator(): #定义一个高阶函数,decorator()相当于定义一个变量,没什么卵用,继续往下走
        start_time = time.time() #截取开始运行时间
        param()   #直接运行param()
        stop_time = time.time()
        print("The runtime of param is %s" %(stop_time - start_time))
    return decorator 
    #返回decorator()函数的内存地址,所以说timer(func_1)的结果实际上就相当于返回了decorator()的内存地址,decorator()      的内存地址有了,想要调用它,把它赋给一个变量func_1,然后func_1()执行就OK了,这时候func_1()执行实际上就是执行的        decorator(),为什么呢?因为timer(func_1)的返回值是decorator这个函数,而decorator这个函数里面做了件什么事呢?装了      时间,param()就是func_1(),于是func_1()就运行了。

@timer  #此处@timer相当于=> func_1 = timer(func_1)
def func_1():
    time.sleep(3)
    print("In the func_1")
@timer
def func_2():
    time.sleep(3)
    print("In the func_2")

func_1()
func_2()

#运行结果:
In the func_1
The runtime of param is 3.0002880096435547
In the func_2
The runtime of param is 3.0007975101470947

#没有改变源代码,也没有修改函数调用方式,为程序添加了新功能。用到了什么功能呢?
函数的嵌套,在原来你已经实现了一个功能的基础之上,外面再套一层函数,然后又引用了高阶函数的定义,最终真正实现了一个装饰器的效果。

那现在如果把func_1( )做修改,把func_1( )增加一个参数:

import time

#装饰器timer
def timer(param):  #timer(func_1),func_1传进来,param = func_1
    def decorator(): #定义一个高阶函数,decorator()相当于定义一个变量,没什么卵用,继续往下走
        start_time = time.time() #截取开始运行时间
        param()   #直接运行param()
        stop_time = time.time()
        print("The runtime of param is %s" %(stop_time - start_time))
    return decorator
    
@timer  #此处@timer相当于=> func_1 = timer(func_1)
def func_1():
    time.sleep(3)
    print("In the func_1")
@timer
def func_2(name):
    print("func_2",name)

func_1()
func_2()

#运行结果:
TypeError: func_2() missing 1 required positional argument: 'name'

出错,func_2()少一个参数name,怎么会少一个参数呢?分析:
@timer相当于func_2 = timer(func_2),现在把func_2传进timer里面,
就是param = func_2,接下来执行decorator(),decorator内嵌的函数没有
执行,声明了函数decorator(),没执行,直接把decorator的内存地址返回
了,那func_2其实是等于decorator,给func_2加上括号func_2()就相当
于decorator加上了括号decorator(),decorator()在执行时执行到
param()实际上执行的就是func_2(),因为func_2传进来了。func_2()
调用的时候要求传参数,但param()没有参数,所以就出错了。所以这时
就要想到,把func_2()的参数传进去怎么做呢?func_2()里面加一个
name,相当于decorator()里面加一个name,如下:
将上面代码中 def decorator():修改为def decorator(*args,**kwargs):
            param(*args,**kwargs)修改为param(*args,**kwargs)

 装饰器之高级版:

现在我有一个需求,有一个公司的网站,这个网站有很多的页面,现在你来写这个网站,咱们现在来模拟一下,一个页面就代表一个函数。在之前的情况下,这个网站是谁都可以随便登录的,没有任何验证。现在,你100个页面有20个页面需要登录才能看到,所以是不是需要在这20个页面里面加上验证功能,大致思想就是这样了。代码实现:

那现在有一个问题,home( )执行完之后,没有返回任何数据。现在给它加上返回数据(返回值)。将上面的代码中的:

def home():  #用户页面,需要登录
    print("Welcome to home page!")

home()

改为:

def home():  #用户页面,需要登录
    print("Welcome to home page!")
    return "from home"

print(home( ))

#运行结果如下图:

home( )结果有吗?没有!这个装饰器虽然把home( )装饰了,也没有改变源代码和调用方式,但是你把人家的结果给改变了!home( )需要结果,你却没结果!这个时候我们该怎么做呢?怎么把home( )的结果给获取到呢?我们来想一想,其实最终执行home( )是不是由func(*args,**kwargs)执行的,这个func(*args,**kwargs)有返回吗?没有返回,它就没有了鸭!意思就是,func(*args,**kwargs)的执行结果如果是home( )的情况下,是不是就是from home鸭?但是这个执行结果有传给谁吗?没有传给谁是不是就没了鸭,那当然就没了,最后就打印了一个上图中的None!

你调用home( )的时候,实际上就在调用wrapper( ),wrapper( )执行力,它有返回值吗?没有!没有返回值,wrapper( )当然就为空了。所以怎么办呢?把func(*args,**kwargs)前面加个return就ok了,加完之后运行如图(又返回结果"from home"了):

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值