Python:装饰器

Decorator  #装饰器

装饰器是什么?
顾名思义,就是用来“装饰”的:

# 语法糖
@xxx

装饰器是一个很著名的设计模式,(也和开闭原则有关)经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

#便于复习首先记录最常用的通用装饰器:

通用装饰器

 

def wrapper(func):
    def inner(*args, **kwargs):
        # print('check..')            # 装饰内容 
        func(*args, **kwargs)         # 执行函数
        ret = fun(xxx)                # 若函数带返回值,return一下
        return ret 
    return inner

@wrapper
def test():
    print('kumata so cool !')

@wrapper
def test1():
    print('kumata 666')
    return 'python'

@wrapper
def test2(a):
    print('I say kumata you say %d ' % a)

test()
test1()
test2(666)

#运行结果
kumata so cool !
kumata 666
I say kumata you say 666

 

 

 

 

 

 

一、引入装饰器

以解决一个问题来理解学习装饰器

以业务(前台)、 服务(后台)的例子:

  • 业务:与客户打交道,在需要的时候,调用服务部门提供的接口
  • 服务:不直接与客户打交道。在后台运行。底层、核心算法、网络
'''服务部门和业务部门的例子'''

# 服务端部门写装饰器
# 验证的函数
def wrapper(fun):
    def check():
        # 验证第一项内容
        # if not xxx
        #     return
        print('check1..done')
        # 验证第二项内容
        print('check2..done')
        # 验证第三项内容
        print('check3..done')
        #上面验证通过后就调用原来的函数
        fun()
    return check


# 业务部门使用接口
'''部门1、2验证,部门3、4不验证'''
@wrapper
def fun1():
    print('hello! I\'m department 1!')

@wrapper
def fun2():
    print('hi! I\'m department 2!')

def fun3():
    print('hi! I\'m department 3!')

def fun4():
    print('hi! I\'m department 4!')

fun1()
fun2()
fun3()
fun4()

#输出结果
check1..done
check2..done
check3..done
hello! I'm department 1!

check1..done
check2..done
check3..done
hi! I'm department 2!

hi! I'm department 3!

hi! I'm department 4!

二、装饰器原理

'''装饰器原理'''

''' 服务部门 '''

# 把要调用的函数作为一个参数传入wrapper()中
def wrapper(fun):
    def check():
        # 验证
        print('check done')
        # 调用原来的函数
        fun()
    return check

''' 业务部门 '''

# # 不用语法糖的写法
# def fun1():
#     print('---fun1---')
#
# def fun2():
#     print('---fun2---')

# f1 = wrapper(fun1)
# f1()
#
# f2 = wrapper(fun2)
# f2()

# 使用语法糖
@wrapper   # f1 = wrapper(fun1)   f1()
def fun1():
    print('---fun1---')

@wrapper
def fun2():
    print('---fun2---')

fun1()
fun2()

三、执行时机

了解了装饰器的原理后,对它的执行时机再写一下

#当python解释器执行到@wrapper时,就开始进行装饰了,相当于执行了代码:test = wrapper(test)

def wrapper(fun):
    print('...装饰器开始装饰...')
    def inner():
        print('...验证权限...')
        fun()
    print('...验证结束...')

    return inner

@wrapper    # test = wrapper(test)
def test():
    print('test')

#不写test()来调用的输出结果
...装饰器开始装饰...
...验证结束...

test()
#输出结果
...装饰器开始装饰...
...验证权限...
test
...验证结束...

四、两个装饰器执行流程和装饰结果

当有两个或两个以上装饰器装饰一个函数时

def wrapper1(fun):
    print('----1-1----')
    def inner():
        print('----1-2----')
        return '<b>' + fun() + '</b>'
    return inner

def wrapper2(fun):
    print('----2-1----')
    def inner():
        print('----2-2----')
        return '<i>' + fun() + '</i>'
    return inner

@wrapper1
@wrapper2
def test():
    print('----3-1----')
    print('----3-2----')
    return 'hello python decorator'

ret = test()
print(ret)

#输出结果
----2-1----
----1-1----
----1-2----
----2-2----
----3-1----
----3-2----
<b><i>hello python decorator</i></b>

分析如下:

  1. 通过上面装饰时机的介绍,我们可以知道,在执行到 @wrapper1 的时候,需要对下面的函数进行装饰,此时解释器继续往下走,发现并不是一个函数名,而又是一个装饰器,
  2. 这时候,@wrapper1 装饰器暂停执行,而接着执行接下来的装饰器 @wrapper2 ,接着把test函数名传入装饰器函数,从而打印 ’2-1’
  3. 在 wrapper2 装饰完后,此时的 test 指向 wrapper2 的 inner 函数地址,这时候又返回来执行 @wrapper1,接着把新 test 传入 wrapper2 装饰器函数中,因此打印了’1-1’
  4. 在调用 tes t函数的时候,根据上述分析,此时 test 指向 wrapper1.inner函数,因此会先打印‘1-2‘
  5. 接下来,在调用fun()的时候,其实是调用的wrapper2.inner()函数,所以打印 ‘2-2‘
  6. 在wrapper2.inner中,调用的fun其实才是我们最原声的test函数,所以打印原test函数中的‘3-1‘‘3-2‘
  7. 所以在一层层调完之后,打印的结果为<b><i>hello python decorator</i></b> 。

五、对有参函数进行装饰

以上为无参数函数使用,在使用中,有的函数可能会带有参数,那么这种如何处理呢? 

def wrapper(fun):
    """
    如果原函数有参数,那闭包函数必须保持参数个数一致,并且将参数传递给原方法
    """
    def inner(name):
        """
        如果被装饰的函数有行参,那么闭包函数必须有参数
        :param name:
        :return:
        """
        print('w\'s inner is called')
        fun(name)
    return inner

@wrapper
def hello(name):
    print('hello ' + name)

hello('dz')

 #运行结果
w's inner is called
hello dz

多个或者不定长参数

def wrapper(fun):
    def inner(*args,**kwargs):
        print('lets go')
        fun(*args,**kwargs)
    return inner


@wrapper
def fun1(a):
    print('hello' + a)

fun1('kumata')

@wrapper
def fun2(a,b):
    print('%d + %d = %d' %(a,b,a+b))

fun2(3,8)

#输出结果
lets go
hellokumata
lets go
3 + 8 = 11

六、对带返回值的函数进行装饰

def wrapper(fun):
    def wrapper_in():
        print('check')
        return fun()
    return wrapper_in

@wrapper
def fun1():
    print('1111')
    return '2222'

fun1()         # 输出fun1()
print('------')
print(fun1())  #输出fun1()返回值

# 输出结果
check
1111
------
check
1111
2222

七、带参数的装饰器

写完对带参数的函数和有返回值的函数进行装饰,还有一个带参数的装饰器

'''
带有参数的装饰器能够起到在运行时,有不同的功能
先执行wrapper('boss'),返回wrapper_fun函数的引用
@wrapper_fun
使用@wrapper_fun对fun进行装饰
嗯..套多了一层的意思啦
'''

def wrapper(param):        # param = 'boss'
    def wrapper_fun(fun):  # fun = fun1/fun2
        def inner():
            print(' %s say:' % param)
            fun()
        return inner
    return wrapper_fun

@wrapper('boss')  # fun1 = wrapper('boss')(fun1)
def fun1():
    print('1!!')


@wrapper('boss')  # fun2 = wrapper('boss')(fun2)
def fun2():
    print('2!!')

fun1()
fun2()

#输出结果
boss say:
1!!
boss say:
2!!

八、类装饰器

装饰器函数其实是一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。 
在python中,一般callable对象都是函数,但是也有例外。比如只要某个对象重写了call方法,那么这个对象就是callable的。

#当创建一个对象后,直接去执行这个对象,那么是会抛出异常的,因为他不是callable,无法直接执行,但进行修改后,就可以直接执行调用了

class Test(object):
    def __call__(self, *args, **kwargs):
        print('call called')

t = Test()
print(t())

#运行结果
call called


#下面引入正题,看一下如何用类装饰函数
class Test(object):
    def __init__(self, func):
        print('test init')
        print('func name is %s ' % func.__name__)
        self.__func = func

    def __call__(self, *args, **kwargs):
        print('装饰器中的功能')
        self.__func()

@Test
def test():
    print('this is test func')

test()

#运行结果
test init
func name is test 
装饰器中的功能
this is test func

#和之前的原理一样,当python解释器执行到到@Test时,会把当前test函数作为参数传入Test对象,调用init方法,同时将test函数指向创建的Test对象,那么在接下来执行test()的时候,其实就是直接对创建的对象进行调用,执行其call方法。

 

学习参考资料:

python装饰器简介---这一篇也许就够了

知乎问答:如何理解Python装饰器?

Python装饰器与面向切面编程

posted @ 2018-05-23 15:44  kumata  阅读(225)  评论(0编辑  收藏  举报