Python小白学习之路(二十四)—【装饰器】

装饰器

一、装饰器的本质

装饰器的本质就是函数,功能就是为其他函数添加附加功能。

利用装饰器给其他函数添加附加功能时的原则:

        1.不能修改被修饰函数的源代码
        2.不能修改被修饰函数的调用方式


举例:计算以下一段程序执行时间

#程序:计算0—19的和
def cal(l):
    res = 0
    for i in l:
        res += i
    return res
print(cal(range(20)))
#给上述程度增加时间模块
import time
def cal(l):
    start_time = time.time()
    res = 0
    for i in l:
        time.sleep(0.1)
        res += i
    stop_time = time.time()
    print('函数的运行时间是%s'%(stop_time - start_time))
    return res
print(cal(range(20)))

#执行结果
函数的运行时间是2.0001144409179688    
190
#上述增加的时间模块
违反了开放封闭原则,改变了cal()函数的源代码,不是实现装饰器的功能。
因此可以把统计时间的函数单独写出来。并且满足以上两个原则

二、怎么样实现一个基本的装饰器(装饰器的知识储备)

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

1.高阶函数

 

  • 函数接受的参数是一个函数名
  • 函数的返回值是一个函数名
  • 满足上述条件的任何一个都可以是高阶函数


高阶函数类型一:函数接受的参数是一个函数名

#举例:
def name1(n):
    print(n)
    n('xhg')
def name2(name):
    print('my name is %s' %name)
name1(name2)
#执行结果
<function name2 at 0x00E194F8>
my name is xhg
#name1()是一个高阶函数,其接受的参数是函数名name2
#程序分析
#函数名name2是函数name2的内存地址。传给函数name1,即参数n=函数name2的内存地址,因此打印的结果为函数name2的内存地址
#n('xhg'),只执行name2('xhg')

利用‘函数接受的参数是一个函数名’这个思想,为函数name2()添加一个统计时间的功能

import time 
def name1(n):
    start_time = time.time()
    n('xhg')
    stop_time = time.time()
    print('函数的运行时间是%s'%(stop_time - start_time))
def name2(name):
    time.sleep(2)
    print('my name is %s' %name)
name1(name2)

#执行结果
my name is xhg
函数的运行时间是2.0001144409179688
#上述程序虽然实现了增加时间模块功能,虽然不改变函数name2()的代码,但是改变了函数name()的调用方式
#因此函数name1()不是装饰器

高阶函数类型二:函数的返回值是一个函数名

#举例:
def name1():
    print('from name1')
def name2():
    print('from name2')
    return name1       
n = name2()
n()

#执行结果
from name2
from name1
#程序分析:
#函数name2()中的返回值中包含函数名name1,所以函数name2()为高阶函数
#将函数名name1赋值给变量n,函数名为该函数内存地址
#n()为执行函数name1

利用‘函数的返回值是一个函数名’这个思想,为函数name2()添加一个统计时间的功能

import time
def name1(n):
    start_time = time.time()
    n('xhg')
    stop_time = time.time()
    print('函数的运行时间是%s' % (stop_time - start_time))
    return n
def name2(name):
    time.sleep(2)
    print('my name is %s' %name)
name2 = name1(name2)
name2('xhg')

#执行结果
my name is xhg
函数的运行时间是2.0001144409179688
my name is xhg
#程序分析:计算函数name2()运行时间的函数模块name1,没有函数name2()改变调用方式,也没有改变数name2()的源代码。
#但是多执行了一次,该装饰器设计不合格

#结论:单独利用高阶函数无法实现装饰器的功能

2.函数嵌套

函数嵌套实际上就是在函数中又定义了一个函数

#举例:

def first():
    print('from first ')
    def second():
        print('from second')
        def third():
            print('from third')
        third()
    second()
first()

#执行结果
from first 
from second
from third

#程序分析


3.闭包

关于闭包这块,我没有太理解了。以下的知识摘自这个博文,对理解闭包挺有帮助的。

https://www.cnblogs.com/guobaoyuan/articles/6756763.html

闭包:首先必须是内部定义的函数,该函数包含对外部作用域而不是全局作用域名字的引用

定义:内部函数的代码包含对外部函数的代码的引用,但一定不是对全局作用域的引用

闭包的基本形式是:

在函数F1中,定义F2,F2只能引用F1定义的变量,之后F1函数返回F2的函数名字

这样就保证了可以将F1的执行结果赋予给一个变量,该变量可以在之后的任何时刻随时可以运行

#举例理解:
x = 1000
def f1():
    x = 1
    def f2():
        print(x)
    return f2
f = f1()
print(f)
f()
x = 123

#执行结果
<function f1.<locals>.f2 at 0x003394B0>
1

#顺便提一句,要想充分理解这边程序执行的情况,需要对作用域以及变量那边有清楚的认识

使用闭包的好处:自带状态即变量,可以不用传参就用,方便。

  • 闭包(closure)是函数式编程的重要的语法结构。
  • 不同的语言实现闭包的方式不同。
  • Python以函数对象为基础,为闭包这一语法结构提供支持的
  • (我们在特殊方法与多范式中,已经多次看到Python使用对象来实现一些特殊的语法)。
  • Python一切皆对象,函数这一语法结构也是一个对象。
  • 在函数对象中,我们像使用一个普通对象一样使用函数对象,比如更改函数对象的名字,或者将函数对象作为参数进行传递。

三、装饰器的框架

有了以上三个知识为基础铺垫,我们可以搭出来一个装饰器的框架模型

以计算程序执行时间的功能函数为例

def timmer(func)
    def wrapper():
        func()
    return wrapper

根据上述框架来写一个计算程序执行时间的功能的装饰器

import time
def timmer(func):
    def wrapper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print('程序运行时间是%s' % (stop_time - start_time))
    return wrapper
def test():
    time.sleep(3)
    print('test程序运行完毕')
test = timmer(test)
test()

#执行结果
test程序运行完毕
程序运行时间是3.000171661376953

#程序分析

#timmer(test) 将test()函数的函数名传给timmer函数,实际上是传递的是test的地址
#执行timmer函数的结果是得到wrapper的地址,即test=wrapper的地址
#test()实际上是在执行wrapper函数

注意:@timmer <===> test = timmer(test)

所以,完美的装饰器诞生啦!!!

import time
def timmer(func):
    def wrapper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print('程序运行时间是%s' % (stop_time - start_time))
    return wrapper
@timmer
def test():
    time.sleep(3)
    print('test程序运行完毕')
test()

四、带返回值的装饰器

#对上面写的这个程序进行一个小小的加工,使得该装饰器有返回值
import time
def timmer(func):
    def wrapper():
        start_time = time.time()
        res = func()
        stop_time = time.time()
        print('程序运行时间是%s' % (stop_time - start_time))
        return res
    return wrapper
@timmer
def test():
    time.sleep(3)
    print('test程序运行完毕')
    return '这是test函数的返回值'
res = test()
print(res)

#执行结果
test程序运行完毕
程序运行时间是3.0001718997955322
这是test函数的返回值
#程序理解
#该程序的理解重点还是要清楚 test() 这一步执行的是哪个函数,分析同上,实际执行 wrapper() 函数
#res = func() 实际执行test()函数,并将test函数的返回值赋值给res变量
#wrapper函数执行完,将局部变量res的值通过return返回给全局变量res

五、带可变长度参数的装饰器

#回顾:

  • 参数组(非固定长度的参数)
  • *args 针对位置参数传递
  • **kwargs 针对关键字参数传递
  • *表传递的数据类型为列表
  • **表传递的数据类型为字典
  • 位置参数,必须一一对应,缺一不行,多一也不行
  • 关键字参数,无需一一对应,缺一不行,多一也不行
  • 关键字参数和位置参数混合使用时,位置参数必须在关键字参数左边
#将上述程序进行修改
import time
def timmer(func):
    def wrapper(*args,**kwargw):
        start_time = time.time()
        func(*args,**kwargw)
        stop_time = time.time()
        print('程序运行时间是%s' % (stop_time - start_time))
    return wrapper
@timmer
def test1(name,age):
    time.sleep(3)
    print('test程序运行完毕,名字是%s,年龄是%s' %(name,age))
@timmer
def test2(name,age,gender):
    time.sleep(3)
    print('test程序运行完毕,名字是%s,年龄是%s,性别是%s' %(name,age,gender))
test1('xhg',age = 18)
test2('xhg', 18, gender = 'male')

#执行结果
test程序运行完毕,名字是xhg,年龄是18
程序运行时间是3.000171422958374
test程序运行完毕,名字是xhg,年龄是18,性别是male
程序运行时间是3.000171422958374
    

#写在后面

时间很快,2018年很快结束

研究生的日子,过得那么单调乏味

感觉还是本科那会逍遥自在

我是一个很少追剧 追星 追综艺的人

感觉不像这个时代的

但奇葩说是我一直坚持看的

不管别人怎么评价这个节目  反正我很喜欢

他会让我思考  让我从不同的角度去看待这个世界

在辩论死忙时间这一期节目

我觉得特别精彩

我泪点好像有点低  也可能有些话确实触及到我内心深处

虫仔生病的那段经历讲述  让我看到了每一个人的不容易

每一个成年人都有自己的秘密  都生活的那么不容易 每个人都在扛着  没有放弃  每个人也都扛着很好

每一个辩手都有自己的故事

每一个普通人也有自己的故事

关键是 看你以什么样的心态去看待

我特别喜欢黄执中,他说,美好的事物,不是没有裂痕,而是满是裂痕,却没有崩开

通过别人的故事,来更加清楚豁达看待自己的生活

加油,小伙郭

加油,每一个在努力的人

当你觉得很累的时候,躺下来好好休息一下。因为一切都会好起来的!努力的人,运气都不会太差

猜你喜欢

转载自www.cnblogs.com/guoruxin/p/10099392.html