• python 修饰器(decorator)


    转载:Python之修饰器 - 知乎 (zhihu.com)

    什么是修饰器,为什么叫修饰器

    修饰器英文是Decorator,

    我们假设这样一种场景:古老的代码中有几个很是复杂的函数F1、F2、F3...,复杂到看都不想看,反正我们就是不想改这些函数,但是我们需要改造加功能,在这个函数的前后加功能,这个时候我们很容易就实现这个需求:

    def hi():
        """hi func,假装是很复杂的函数"""
        return 'hi'
    
    def aop(func):
        """aop func"""
        print('before func')
        print(func())
        print('after func')
        
    if __name__ == '__main__':
        aop(hi)

     

    以上是很是简单的实现,利用Python参数可以传函数引用的特性,就可以实现了这种类似AOP的效果。

    这段代码目前没有什么问题,接下来煎鱼加需求:需求为几十个函数都加上这样的前后的功能,而所有调用这些函数地方也要相应地升级。

    看起来这个需求比较扯,偏偏这个需求却是较为广泛:在调用函数的前后加上log输出、在调用函数的前后计算调用时间、在调用函数的前后占用和释放资源等等。

    一种比较笨的方法就是,为这几十个函数逐一添加一个入口函数,针对a函数添加一个a_aop函数,针对b函数添加一个b_aop函数...如此这样。问题也很明显:

    1. 工作量大
    2. 代码变得臃肿复杂
    3. 原代码有多处调用了这些函数,可以会升级不完全

    于是接下来有请修饰器出场,修饰器可以统一地给这些函数加这样的功能:

    def aop(func):
        """aop func"""
        def wrapper():
            """wrapper func"""
            print('before func')
            func()
            print('after func')
        return wrapper
    
    @aop
    def hi():
        """hi func"""
        print('hi')
        
    @aop
    def hello():
        """hello func"""
        print('hello')
    
    if __name__ == '__main__':
        hi()
        hello()
     

     

    以上aop函数就是修饰器的函数,使用该修饰器时只要在待加函数上一行加@修饰器函数名即可,如实例代码中就是@aop。

    加上了@aop后,调用新功能的hi函数就喝原来的调用一样:就是hi()而不是aop(hi),也意味着所有调用这些函数的地方不需要修改就可以升级。

    简单地来说,大概修饰器就是以上的这样子。

    @是个什么

    对于新手来说,上面例子中,@就是一样奇怪的东西:为什么这样子用就可以实现煎鱼需求的功能了。

    其实我们还可以不用@,煎鱼换一种写法:

    def hi():
        """hi func"""
        print('hi')
    
    def aop(func):
        """aop func"""
        def wrapper():
            """wrapper func"""
            print('before func')
            func()
            print('after func')
        return wrapper
    
    if __name__ == '__main__':
        hi()
    
        print('')
    
        hi = aop(hi)
        hi()

     

    上面的例子中的aop函数就是之前说过的修饰器函数。

    如例子main函数中第一次调用hi函数时,由于hi函数没叫修饰器,因此我们可以从输出结果中看到程序只输出了一个hi而没有前后功能。

    然后煎鱼加了一个hi = aop(hi)后再调用hi函数,得到的输出结果和加修饰器的一样,换言之:

    @aop 等效于hi = aop(hi)

    因此,我们对于@,可以理解是,它通过闭包的方式把新函数的引用赋值给了原来函数的引用。

    有点拗口。aop(hi)是新函数的引用,至于返回了引用的原因是aop函数中运用闭包返回了函数引用。而hi这个函数的引用,本来是指向旧函数的,通过hi = aop(hi)赋值后,就指向新函数了。

    被调函数加参数

    以上的例子中,我们都假设被调函数是无参的,如hi、hello函数都是无参的,我们再看一眼煎鱼刚才的写的修饰器函数:

    def aop(func):
        """aop func"""
        def wrapper():
            """wrapper func"""
            print('before func')
            func()
            print('after func')
        return wrapper

    很明显,闭包函数wrapper中,调用被调函数用的是func(),是无参的。同时就意味着,如果func是一个带参数的函数,再用这个修饰器就会报错。

    @aop
    def hi_with_deco(a):
        """hi func"""
        print('hi' + str(a))
    
    if __name__ == '__main__':
        # hi()
        hi_with_deco(1)

     

    就是参数的问题。这个时候,我们把修饰器函数改得通用一点即可,其中import了一个函数(也是修饰器函数):

    from functools import wraps
    
    def aop(func):
        """aop func"""
        @wraps(func)
        def wrap(*args, **kwargs):
            print('before')
            func(*args, **kwargs)
            print('after')
    
        return wrap
    
    @aop
    def hi(a, b, c):
        """hi func"""
        print('test hi: %s, %s, %s' % (a, b, c))
    
    @aop
    def hello(a, b):
        """hello func"""
        print('test hello: %s, %s' % (a, b))
    
    if __name__ == '__main__':
        hi(1, 2, 3)
        hello('a', 'b')

     

     

    这是一种很奇妙的东西,就是在写修饰器函数的时候,还用了别的修饰器函数。那也没什么,毕竟修饰器函数也是函数啊,有什么所谓。

    带参数的修饰器

    思路到了这里,煎鱼不禁思考一个问题:修饰器函数也是函数,那函数也是应该能传参的。函数传参的话,不同的参数可以输出不同的结果,那么,修饰器函数传参的话,不同的参数会怎么样呢?

    其实很简单,修饰器函数不同的参数,能生成不同的修饰器啊。

    如,我这次用这个修饰器是把时间日志打到test.log,而下次用修饰器的时候煎鱼希望是能打到test2.log。这样的需求,除了写两个修饰器函数外,还可以给修饰器加参数选项:

    from functools import wraps
    
    def aop_with_param(aop_test_str):
        def aop(func):
            """aop func"""
            @wraps(func)
            def wrap(*args, **kwargs):
                print('before ' + str(aop_test_str))
                func(*args, **kwargs)
                print('after ' + str(aop_test_str))
            return wrap
        return aop
    
    @aop_with_param('abc')
    def hi(a, b, c):
        """hi func"""
        print('test hi: %s, %s, %s' % (a, b, c))
    
    @aop_with_param('pppppp')
    def hi2(a, b, c):
        """hi func"""
        print('test hi: %s, %s, %s' % (a, b, c))
    
    if __name__ == '__main__':
        hi(1, 2, 3)
        print('')
        hi2(2, 3, 4)

     

    同样的,可以加一个参数,也可以加多个参数,这里就不说了。

    修饰器类

    大道同归,逻辑复杂了之后,人们都喜欢将函数的思维层面抽象上升到对象的层面。原因往往是对象能拥有多个函数,对象往往能管理更复杂的业务逻辑。

    显然,修饰器函数也有对应的修饰器类。写起来也没什么难度,和之前的生成器一样简单:

    from functools import wraps
    
    class aop(object):
        def __init__(self, aop_test_str):
            self.aop_test_str = aop_test_str
    
        def __call__(self, func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                print('before ' + self.aop_test_str)
                func()
                print('after ' + self.aop_test_str)
    
            return wrapper
            
    @aop('pppppp')
    def hi():
        print('hi')

    看得出来,这个修饰器类也不过是多了个__call__函数,而这个__call__函数的内容和之前写的修饰器函数一个样!而使用这个修饰器的方法,和之前也一样,一样的如例子中的@aop('pppppp')。

    甚至,煎鱼过于无聊,还试了一下继承的修饰器类:

    class sub_aop(aop):
        def __init__(self, sub_aop_str, *args, **kwargs):
            self.sub_aop_str = sub_aop_str
            super(sub_aop, self).__init__(*args, **kwargs)
    
        def __call__(self, func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                print('before ' + self.sub_aop_str)
                super(sub_aop, self).__call__(func)()
                print('after ' + self.sub_aop_str)
            return wrapper
            
    @sub_aop('ssssss', 'pppppp')
    def hello():
        print('hello')
        
    if __name__ == '__main__':
        hello()
  • 相关阅读:
    POJ 1113 Wall
    POJ 2159 Ancient Cipher
    POJ 3253 Fence Repair
    HDU 5444 Elven Postman
    HDU 5432 Pyramid Split
    数据库 组合查询
    数据库 简单的数据查询
    数据库 聚合函数与分组
    数据库 使用DML语句更改数据
    数据库的数据完整性
  • 原文地址:https://www.cnblogs.com/zhiminyu/p/14714443.html
Copyright © 2020-2023  润新知