Python -- 面向对象进阶之--装饰器

装饰器

一、介绍:

装饰器实际上就是为了给某程序增添功能,但该程序已经上线或已经被使用,那么就不能大批量的修改源代码,这样是不科学的也是不现实的,因此就产生了装饰器,使得其满足:

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

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

  3. 满足1、2的情况下给程序增添功能

那么根据需求,同时满足了这三点原则,这才是我们的目的。因为,下面我们从解决这三点原则入手来理解装饰器。

 

二、先明白这段代码

def foo():
    print('foo')

foo #表示是函数
foo() #表示执行foo函数



def foo():
    print('foo')

foo = lambda x: x + 1
foo() # 执行下面的lambda表达式,而不再是原来的foo函数

 

三、需求来了

初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作、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聊了个天:

老大说:

写代码要遵循 开放封闭 原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
封闭:已实现的功能代码块
开放:对扩展开发
如果将开放封闭原则应于在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码,老大就给了Low BBB一个实现先案:

def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        func()
    return inner

@w1
def f1():
    print('f1')

@w1
def f2():
    print('f2')

@w1
def f3():
    print('f3')

@w1
def f4():
    print('f4')

对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作。

Low BBB问了下老大,这段代码的内部执行原理是什么呢?

Python -- 面向对象进阶之--装饰器

Python -- 面向对象进阶之--装饰器

python解释器就会从上到下解释代码,步骤如下:
1. def w1(func): ==>将w1函数加载到内存
2. @w1(不考虑后面的f2)
没错, 从表面上看解释器仅仅会解释这两句代码,因为函数在没有被调用之前其内部代码不会被执行。
从表面上看解释器着实会执行这两句,但是 @w1 这一句代码里却有大文章, @函数名是python的一种语法糖。

上例@w1内部会执行以下操作:

1、执行w1函数

执行w1函数 ,并将 @w1下面的函数作为w1函数的参数,即:@w1等价于 w1(f1) 所以,内部就会去执行inner函数:

2、w1的返回值

将执行完的w1函数返回值赋值给@w1下面的函数的函数名f1即将w1的返回值再重新赋值给 f1;

所以,以后业务部门想要执行f1函数时,就会执行新f1函数,在新f1函数内部先执行验证,再执行原来的f1函数,然后将原来f1 函数的返回值返回给了业务调用者。

 

四、多个装饰器

#定义函数:完成包裹数据
def makeBold(fn):
    def wrapped():
        print("----1---")
        return "<b>" + fn() + "</b>"
    return wrapped

#定义函数:完成包裹数据
def makeItalic(fn):
    def wrapped():
        print("----2---")
        return "<i>" + fn() + "</i>"
    return wrapped

@makeBold
@makeItalic
def test3():
    print("----3---")
    return "hello world-3"

ret = test3()
print(ret)

输出结果:

----1---
----2---
----3---
<b><i>hello world-3</i></b>

当Python解释器执行到@makeBold语句时,会判断下面语句是不是一个函数,如果是函数就开始装饰,如果不是则不进行装饰。因为@makeBold语句后面是@makeItalic,则@makeItalic会去判断下面的语句是不是一个函数,如果是函数就开始进行装饰。所以当包含多个装饰器时,会先用后面的装饰器进行装饰。

Python -- 面向对象进阶之--装饰器

 

五、装饰器什么时候进行装饰:

def w1(func):
    print("---正在装饰1----")
    def inner():
        print("---正在验证权限1----")
        func()
    return inner

def w2(func):
    print("---正在装饰2----")
    def inner():
        print("---正在验证权限2----")
        func()
    return inner

#只要python解释器执行到了这个代码,那么就会自动的进行装饰,而不是等到调用的时候才装饰的
@w1
@w2
def f1():
    print("---f1---")

#在调用f1之前,已经进行装饰了
f1()

输出结果:

---正在装饰2----
---正在装饰1----
---正在验证权限1----
---正在验证权限2----
---f1---

Python -- 面向对象进阶之--装饰器

 

六. 装饰器示例

例1:无参数的函数

def func(functionName):
    print("---func---1---")
    def func_in():
        print("---func_in---1---")
        functionName()
        print("---func_in---2---")

    print("---func---2---")
    return func_in

@func
def test():
    print("----test----")

#test = func(test)
test()

输出结果:

---func---1---
---func---2---
---func_in---1---
----test----
---func_in---2---

上面的代码装饰器执行行为可理解成
test = func(test) 
#test先作为参数赋值给functionName(即functionName指向了test),test接收指向func返回的func_in
test()
#调用test(),即等价调用func_in()
#内部函数func_in被引用,所以外部函数的functionName变量(*变量)并没有释放
#functionName里保存的是原test函数对象

其实前面讲的被装饰的函数都是不带参数的,下面我们讲装饰器对有参数的函数进行装饰。

例2:被装饰的函数有参数

def func(functionName):
    print("---func---1---")
    def func_in(a, b):#如果a,b 没有定义,那么会导致test(11,22)行的调用失败
        print("---func_in---1---")
        functionName(a, b)#如果没有把a,b当做实参进行传递,那么会导致调用test()函数失败
        print("---func_in---2---")

    print("---func---2---")
    return func_in

@func
def test(a, b):
    print("----test-a=%d,b=%d---"%(a,b))

test(11,22)

输出结果:

---func---1---
---func---2---
---func_in---1---
----test-a=11,b=22---
---func_in---2---

例3:被装饰的函数有不定长参数

def func(functionName):
    print("---func---1---")
    def func_in(*args, **kwargs):#采用不定长参数的方式满足所有函数需要参数以及不需要参数的情况
        print("---func_in---1---")
        functionName(*args, **kwargs)#这个地方,需要写*以及**,如果不写的话,那么args是元组,而kwargs是字典
        print("---func_in---2---")

    print("---func---2---")
    return func_in

@func
def test(a, b, c):
    print("----test-a=%d,b=%d,c=%d---"%(a,b,c))

@func
def test2(a, b, c, d):
    print("----test-a=%d,b=%d,c=%d,d=%d---"%(a,b,c,d))

test(11,22,33)
test2(44,55,66,77)

输出结果:

---func---1---
---func---2---
---func---1---
---func---2---
---func_in---1---
----test-a=11,b=22,c=33---
---func_in---2---
---func_in---1---
----test-a=44,b=55,c=66,d=77---
---func_in---2---

例4:装饰器中的return

def func(functionName):
    print("---func---1---")
    def func_in():
        print("---func_in---1---")
        ret = functionName() #保存返回来的haha
        print("---func_in---2---")
        return ret #把haha返回到ret = test()行处的调用

    print("---func---2---")
    return func_in

@func
def test():
    print("----test----")
    return "haha"

ret = test()
print("test return value is %s"%ret)

输出结果:

---func---1---
---func---2---
---func_in---1---
----test----
---func_in---2---
test return value is haha

例5:通用装饰器

def func(functionName):
    def func_in(*args, **kwargs):
        print("-----记录日志-----")
        ret = functionName(*args, **kwargs)
        return ret

    return func_in

@func
def test():
    print("----test----")
    return "haha"

@func
def test2():
    print("----test2---")

@func
def test3(a):
    print("-----test3--a=%d--"%a)

ret = test()
print("test return value is %s"%ret)

a = test2()
print("test2 return value is %s"%a)

test3(11)

输出结果:

-----记录日志-----
----test----
test return value is haha
-----记录日志-----
----test2---
test2 return value is None
-----记录日志-----
-----test3--a=11--

例6、带有参数的装饰器

def func_arg(arg):
    def func(functionName):
        def func_in():
            print("---记录日志-arg=%s--"%arg)
            if arg=="heihei":
                functionName()
                functionName()
            else:
                functionName()
        return func_in
    return func

#1. 先执行func_arg("heihei")函数,这个函数return 的结果是func这个函数的引用
#2. @func
#3. 使用@func对test进行装饰
@func_arg("heihei")
def test():
    print("--test--")

#带有参数的装饰器,能够起到在运行时,有不同的功能
@func_arg("haha")
def test2():
    print("--test2--")

test()
test2()

输出结果:

---记录日志-arg=heihei--
--test--
--test--
---记录日志-arg=haha--
--test2--