[原]Python装饰器

李余通 18/04/10 18:20:13

很多时候,我们需要对已经实现的功能进行扩展,即增加新的功能,那么,最容易想到的就是就是对原有功能进行修改,这个时候免不了要修改原始代码,但面向对象编程的一个思想是开放封闭原则,即:

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

已实现的功能可以被扩展,不能被修改

需求来了

现在有一个函数

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()原始的功能。
下面看看@语法糖做了什么工作:

  1. 将装饰器下面的函数作为参数传给装饰器函数
    这样一来就相当于outer(do)(msg)
  2. 用装饰器对应的函数的返回值替换原始函数名
    即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()时,不再替换,仅仅是调用被替换的新函数。

作者:baidu_35085676 发表于 2018/04/10 18:20:13 原文链接 https://blog.csdn.net/baidu_35085676/article/details/79885374
阅读:96