很多时候,我们需要对已经实现的功能进行扩展,即增加新的功能,那么,最容易想到的就是就是对原有功能进行修改,这个时候免不了要修改原始代码,但面向对象编程的一个思想是开放封闭原则,即:
- 开放:对扩展开发
- 封闭:对已实现的功能模块
已实现的功能可以被扩展,不能被修改
需求来了
现在有一个函数
def do(msg):
print("do %s..." % msg)
现在要求执行do之前打印一句before do...
,执行完之后打印一句end do...
很容易想到这样的做法:
def other_do(msg):
print('before do...')
do(msg)
print('end do...')
功能达到了,但是有一个问题,我实际调用的函数已经不是do(msg)
,而是other_do(msg)
了,那么有没有什么别的方法呢?
答案是装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
那么Python中如何实现装饰器的呢?使用的是闭包 + @语法糖
先用闭包改写do的功能:
def outer(func):
def inner(msg):
print('before do...')
func(msg)
print('after do...')
return inner
def do(msg):
print('do %s...' % msg)
msg = 'chicken'
outer(do)(msg)
可以看出来调用还是很麻烦。
# coding=utf-8
def outer(func):
def inner(msg):
print('before do...')
func(msg)
print('after do...')
return inner
@outer
def do(msg):
print('do %s...' % msg)
msg = 'chicken'
do(msg)
这样,我么还和以前一样调用函数,但是函数的功能已经改变,并且我们也没有修改do()
原始的功能。
下面看看@语法糖做了什么工作:
- 将装饰器下面的函数作为参数传给装饰器函数
这样一来就相当于outer(do)(msg) - 用装饰器对应的函数的返回值替换原始函数名
即do(msg) = outer(do)(msg)
这样装饰器的实现就很明了了。
带参装饰器
现在我们有新的需求了,这个装饰器需要根据参数的不同,有着不同的效果,比如
@outer(1,2)
def func():
pass
需要实现下面的效果:
print(1)
func()
print(2)
那么,首先,我们分析以下装饰器的用法@outer(a,b)
到底是@先生效还是()先执行,好了,实际上是()先执行,那么类比无参数装饰器,可以写出来:
def outer(a,b):
def wapper(func):
def inner(*args,**kwargs):
print(a)
val = func(*args,**kwargs)
print(b)
return val
return inner
return wapper
实际上,我们作一次转换,他就想到于这么调用outer(a,b)(func)(*args,**kwargs)
这么看来,是不是很明了了?
同一个函数被多个装饰器装饰
def outer1(func):
def inner():
print('outer 1')
func()
return inner
def outer2(func):
def inner():
print('outer 2')
func()
return inner
@outer2
@outer1
def did():
print('did something...')
did()
先来猜测以下结果,是先输出outer 2还是outer 1呢?
outer 2
outer 1
did something...
结果是2先输出。
首先解释器读到@outer2
这句话,把
@outer1
def did()
当作outer2的参数,那么实际上代码就变成类似于这样
print('outer 2')
@outer1
def did()
继续变成
print('outer 2')
print('outer 1')
did()
顺序自然出来了。
再说一说解释器怎么处理装饰器
def outer(func):
print('ahaha')
def inner(msg):
print('before do...')
func(msg)
print('after do...')
return inner
print('1')
@outer
def do(msg):
print('do %s...' % msg)
print('2')
1
ahaha
2
可以看出,解释器在读到@时,就执行了替换操作,以后遇到调用do()时,不再替换,仅仅是调用被替换的新函数。