美文网首页
python装饰器

python装饰器

作者: 壁花烧年 | 来源:发表于2017-06-12 14:42 被阅读0次

    装饰器是程序开发中经常会用到的一个功能,功能就是在运行原来功能基础上,加上一些其它功能,比如权限的验证,比如日志的记录等等。不修改原来的代码,进行功能的扩展。
    比如java中的动态代理,python的注解装饰器。
    其实python的装饰器,是修改了代码。
    装饰器的工作原理:

    def outer(fn):
        print('...outer...')
    
        def inner():
            print('...inner...')
            fn()
    
        return inner
    
    
    def test():
        print('...test...')
    
    test = outer(test)
    print('*'*15)
    test()
    

    结果如下:


    装饰器原理结果.jpg

    装饰器就是引用了闭包的概念。装饰器的工作原理如下:


    装饰器原理.jpg
    现在采用装饰器:
    def outer(fn):
        print('...outer...')
    
        def inner():
            print('...inner...')
            fn()
    
        return inner
    
    
    @outer                       #就相当于 test = outer(test)
    def test():
        print('...test...')
    
    test()
    

    输出结果如下:


    装饰器结果.jpg

    多个装饰器:

    def f1(fn):
        def f1_inner():
            return '<b>'+fn()+'</b>'
        return f1_inner
    
    def f2(fn):
        def f2_inner():
            return '<i>'+fn()+'</i>'
        return f2_inner
    
    @f1
    @f2
    def fun():
        return 'hello world!'
    
    print(fun())
    

    结果如下:

    多个装饰器结果.jpg

    装饰器主要的功能有:
    引入日志
    函数执行时间统计
    执行函数前预备处理
    执行函数后清理处理
    权限检验等场景
    缓存
    之前演示的装饰器都是无参调用的,装饰器的传参是如何实现的:

    def f1(fn):
        def f2(a,b):
            print(a,b)
            fn(a,b)
        return f2
    
    
    @f1
    def fun(a, b):
        print(a+b)
    
    fun(10, 20)
    

    输出结果如下:


    装饰器传参结果.jpg

    这样把参数写死显然不适合装饰器的普遍性,如果有不同参数的函数调用该装饰器,如何改写装饰器:

    from time import ctime, sleep
    
    
    def f1(fn):
        def f2(*args, **kwargs):
            print('%s call at %s'%(fn.__name__, ctime()))
            print(*args, **kwargs)
            fn(*args, **kwargs)
        return f2
    
    
    @f1
    def fun1(a, b):
        print(a+b)
    @f1
    def fun2(a, b, c):
        print(a+b+c)
    
    fun1(10, 20)
    sleep(2)
    fun2(30, 40, 50)
    

    结果如下:


    不定长传参结果.jpg

    以上举得被装饰器装饰过的函数都是直接打印,如果是想返回的值的函数使用该装饰器,那装饰器又不具备普遍性,修改装饰器如下:

    from time import ctime, sleep
    
    
    def f1(fn):
        def f2(*args, **kwargs):
            print('%s call at %s'%(fn.__name__, ctime()))
            return fn(*args, **kwargs)
        return f2
    
    
    @f1
    def fun1(a, b):
        print(a+b)
    @f1
    def fun2(a, b, c):
        return a+b+c
    
    fun1(10, 20)
    sleep(2)
    print(fun2(30, 40, 50))
    

    结果如下:

    return装饰器结果.jpg
    如果闭包中的f2没有return fn(args,kwargs)而是直接fn(args,**kwargs),最后打印得到的结果是None。
    装饰器带参数,在原有装饰器的基础上,设置外部变量。
    from time import ctime, sleep
    
    def timefun_arg(pre="hello"):
        def timefun(func):
            def wrappedfunc():
                print("%s called at %s %s"%(func.__name__, ctime(), pre))
                return func()
            return wrappedfunc
        return timefun
    
    @timefun_arg("wangcai")
    def foo():
        print("I am foo")
    
    @timefun_arg("python")
    def too():
        print("I am too")
    
    @timefun_arg()
    def coo():
        print("I am coo")
    
    foo()
    sleep(2)
    
    too()
    sleep(2)
    
    coo()
    

    输出结果如下:

    带参数的装饰器.jpg
    装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 call() 方法,那么这个对象就是callable的。
    类装饰器:
    class Dog():
        def __init__(self, fun):
            self.__fun = fun
    
        def __call__(self, *args, **kwargs):
            print('...call...')
            return self.__fun(*args, **kwargs)
    
    @Dog                       #foo = Dog(foo)
    def foo():
        print('...foo...')
    
    @Dog
    def foo1(a, b):
        print('...foo1...')
        return a+b
    
    
    foo()
    print(foo1(1,2))
    

    结果如下:

    类装饰器结果.jpg
    当用Dog来装作装饰器对foo函数进行装饰的时候,首先会创建Dog的实例对象
    并且会把foo这个函数名当做参数传递到init方法中,即在init方法中的fun变量指向了foo函数体,foo函数相当于指向了用Dog创建出来的实例对象。当在使用foo()进行调用时,就相当于让这个对象(),因此会调用这个对象的call方法。为了能够在call方法中调用原来foo指向的函数体,所以在init方法中就需要一个实例属性来保存这个函数体的引用,所以才有了self.__fun = fun这句代码,从而在调用call方法中能够调用到foo之前的函数体。

    相关文章

      网友评论

          本文标题:python装饰器

          本文链接:https://www.haomeiwen.com/subject/nmokqxtx.html