Python装饰器小谈

近几日再次研究Python装饰器,对装饰器又有了新的理解和应用。如果这篇文章叫做小谈装饰器,不如重谈装饰器更来得亲切自然。

一.老生常谈

(此标题下的内容方便新手入门装饰器,及大家复习装饰器。由浅入深再次谈谈学习见解和基础知识点,当然本篇文章小谈重点不在于此,对于已学习过或者见解较深的同学请直接向下看二标题新的见解。)

1.闭包

对于装饰器的作用我在此就再次简述一次。装饰器的概念要源于闭包,闭包顾名思义可以理解为在函数里“闭合”的“包裹”的另一个函数。官方是这么说的:在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。,First,先举个栗子,

def test(number):

#定义在内部的函数
    def test_in(number_in):
        print("in test_in 函数, number_in is %d" % number_in)
        #传进来的number参数可以被内部使用,也就是作用域在test函数,而number_in只作用于test_in这个内部的函数中
        return number+number_in
    # 其实这里返回的就是闭包的结果
    return test_in

# 给test函数赋值,这个20就是给参数number
ret = test(20)
# 注意这里的100其实给参数number_in
print(ret(100))
#注 意这里的200其实给参数number_in
print(ret(200))

这样看也许就清晰明了了,重新复习闭包结束。

2.装饰器

在再次重述装饰器前要引用一个网上的段子让大家重新复习一下,
初创公司有N个业务部门,基础平台部门负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:

基础平台提供的功能如下

def f1():
    print('f1')

def f2():
    print('f2')

def f3():
    print('f3')

def f4():
    print('f4')

业务部门A 调用基础平台提供的功能

f1()
f2()
f3()
f4()

业务部门B 调用基础平台提供的功能

f1()
f2()
f3()
f4()

目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。

老大把工作交给 Low B,他是这么做的:
跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。诶,这样一来基础平台就不需要做任何修改了。太棒了,有充足的时间泡妹子…

当天Low B 被开除了…

老大把工作交给 Low BB,他是这么做的:

基础平台提供的功能如下

def f1():
    # 验证1
    # 验证2
    # 验证3
    print('f1')

def f2():
    # 验证1
    # 验证2
    # 验证3
    print('f2')

def f3():
    # 验证1
    # 验证2
    # 验证3
    print('f3')

def f4():
    # 验证1
    # 验证2
    # 验证3
    print('f4')

业务部门不变
业务部门A 调用基础平台提供的功能

f1()
f2()
f3()
f4()

业务部门B 调用基础平台提供的功能

f1()
f2()
f3()
f4()

过了一周 Low BB 被开除了…

老大把工作交给 Low BBB,他是这么做的:
只对基础平台的代码进行重构,其他业务部门无需做任何修改

基础平台提供的功能如下

def check_login():
    # 验证1
    # 验证2
    # 验证3
    pass


def f1():

    check_login()

    print('f1')

def f2():

    check_login()

    print('f2')

def f3():

    check_login()

    print('f3')

def f4():

    check_login()

    print('f4')

老大看了下Low BBB 的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟Low BBB聊了个天:

老大说:
写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

封闭:已实现的功能代码块
开放:对扩展开发

好了,说了这么多,想必对于装饰器还是一头雾水的同学们已经有了一些新的理解,那就是对于在现有功能上有了新的需求的情况下,增加新的功能,重要的是扩展,而不是只是仅仅的修改
那么对于老板给他的解决方案当然就是装饰器啦,下面就是对于他的解决方案。

def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        func()
    return inner
#@符号就是装饰器的使用,在原有基础功能w1上新增加f1功能
@w1
def f1():
    print('f1')
@w1
def f2():
    print('f2')
@w1
def f3():
    print('f3')
@w1
def f4():
    print('f4')

回归正题,对于上述一大堆段子只是为了方便大家理解装饰器的逻辑,当然,在此还要再次还要重申原理

#要增加的验证功能,打印出“验证1,2,3”
def yanzheng(func):
    def inner():
        print("验证1")
        print("验证2")
        print("验证3")
        func()
    return inner

# 底层实现的功能,单纯的只是实现了一个打印“in f1”功能
def f1():
    print("in f1")


# 灵魂代码,调用yanzheng()函数依次传入参数f1执行函数中代码,return返回内层函数打印“验证1,2,3”,
然后执行传入的f1内层函数打印“in f1”。当然此时的f1已经不是当初的f1了,只是名字相同罢了,
做了这所有的操作只是为了将现在的当初的f1方法进行拓展,然后留给用户的接口都是调用相同名字的f1而已,这样在调用的时候就可以无形的避免了更改新增加功能的函数,从而实现了封闭和开放的原则。
f1 = yanzheng(f1)
f1()

再来看一个例子,计算函数运行时间

import time

def gettime(func):
    """统计函数运行时间"""
    #  接收所有 位置参数 关键字参数
    def inner(*args, **kwargs):
        begin = time.time()
        #   接收所有 位置参数 关键字参数 -- 原封不动 传送给func
        # 在调用完成 被修改的函数的功能之后 暂时保存返回值 待整体完成后返回
        ret = func(*args, **kwargs)
        end = time.time()
        print("函数运行所需的时间是 %f秒" % (end-begin))
        return ret
    return inner

@gettime
def f1(number):
    print("in f1 %d" % number)
    for i in range(2):
        time.sleep(1)

    return 520

@gettime
def run(number1, number2):
    time.sleep(1)
    print("in run %d" % (number1 + number2))
    return 131410086
print(f1(100))
print(run(1,999))

print(f1(100))在调用f1(100)时,执行被装饰过的f1,进入gettime(f1),返回inner内层函数–>执行inner增加的功能gettime–>调用f1打印“in f1”和传入的参数值–>return一个值“520”给外部ret–>执行打印执行时间返回ret值并打印出来。
函数的执行流程全部理清楚了,对于传入多个参数的run(1,999)亦是如此。

二、新的发现

对于闭包和装饰器的用法我上面已经再次重复一遍了。大白话来说其实就是参数的作用域和扩展功能的理解,如果把函数比作一张披萨饼,装饰器就是上面的肉片,香肠,马苏里拉奶酪,彩椒……装饰过的披萨饼照样是披萨,只是比初始口味的披萨饼多了更人定制更符合个人需要了而已。
自己在研究装饰器时也掉过坑,请看下面一段代码。

def hahaha(func):
    print("哈哈哈")
    def inner():
        func()
    return inner

def xixixi():
    print("嘻嘻嘻")

xixixi = hahaha(xixixi)
xixixi()

还有这段代码

def hahaha(func):
    print("哈哈哈")
    def inner():
#这里不太一样
        return func() 
    return inner

def xixixi():
    print("嘻嘻嘻")

xixixi = hahaha(xixixi)
xixixi()

输出结果和单纯装饰器没什么两样,而且对于样式来说又是和装饰器长得很像。都是在xixixi()原有的基础上拓展了hahaha()的新功能,使原来的单纯输出“嘻嘻嘻”变成输出装饰过后的输出“嘻嘻嘻”和“哈哈哈”但是请注意确实在功能上一样,而且似乎感觉这样的写法确实没有错误。此时请注意第二行到第五行,在hahaha()函数中的功能确实是输出“哈哈哈”无疑,但是写在了inner()内层函数的外边,在这种情况下调用的流程就变成了执行装饰过后的xixixi()函数时先输出哈哈哈,return返回内层函数执行func()输出“嘻嘻嘻”。结果当然无差,那么有些同学可能会问这不是完全一样么,写在内写在外执行都是这样而且并无大碍,那么其实就掉坑了,对于装饰器来说其实是将装饰过的结果都放在inner函数中的也就是inner函数就是原来将要包装的函数装饰过的新函数。在上述写法中,如果抽象的来理解的话就好像用一根线把新功能和旧功能链接在了一起,在执行旧的功能函数时链出了新拓展的功能。是的!那么你或许会有疑问,这样做其实也是没有问题的啊,既在原有的基础代码上没有做修改,也实现了拓展。不!!如果这样做名义上其实他不能叫做是一个装饰器,为什么呢?如果在调用两次新功能函数时不能继续调用装饰的新功能,如下:

def hahaha(func):
    print("哈哈哈")
    def inner():
        func()
    return inner

# 底层实现的功能
def xixixi():
    print("嘻嘻嘻")
xixixi = hahaha(xixixi)
xixixi()
#比上面代码多增加了这一行
xixixi()

在再次调用xixixi()时输出的不再是“哈哈哈”和“嘻嘻嘻”而是只是一句“嘻嘻嘻”整段代码的输出结果不是大家想的那样输出了四句话,而是三句话。这样在做出我们想要的功能的时候就需要每次都需要装饰后在调用,也就是说装饰的功能函数只能被调用一次。
进行如下改动:

xixixi = hahaha(xixixi)
xixixi()
#这样每次执行前都进行装饰才能调用成功
xixixi = hahaha(xixixi)
xixixi()

通过上面的说明想必大家和我一样也对Python的装饰器有了新的理解和更加深入的认知。应该也有不少同学和我一样掉进过坑里,希望我的这篇文章能给大家带来新的学习体验。

    原文作者:PeterPZ
    原文地址: https://www.jianshu.com/p/a4d30ebb3e05
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞