美文网首页
闭包和装饰器

闭包和装饰器

作者: MononokeHime | 来源:发表于2018-09-04 21:54 被阅读0次

    闭包

    之前一直不明白闭包的定义-内层函数引用了外层函数的变量(包括它的参数)就构成了闭包。我觉得也没什么了不起的,干嘛还专门起一个别扭的名字闭包呢。后来发现,其实并不是想的这么简单,内层函数调用外层变量的时候,这时候外层函数已经调用结束返回了,理论上外层函数所有的局部变量都已经释放,所以内层函数不应该拿到外层函数的变量(包括它的参数)。闭包就是将使用到的变量(称为环境变量)绑定到内层函数的__closure__属性上。

    image.png
    def make_averager():
        series = []
    
        def averager(new_value):
            series.append(new_value)
            total = sum(series)
            return total/len(series)
        return averager
    
    avg = make_averager()
    print(avg.__code__.co_freevars[0])  # series
    print(avg.__closure__[0].cell_contents)  # []
    
    avg(10)
    avg(12)
    print(avg.__code__.co_freevars[0])  # series
    print(avg.__closure__[0].cell_contents) # [10, 12]
    

    装饰器

    装饰器就是在不改变原函数的调用方式的情况下,在函数的前后添加功能。装饰器完美的展示了开放封闭原则,即对扩展开放,对修改封闭。

    关于装饰器的一些概念

    • 普通装饰器:@decorator
    • 装饰器工厂函数:本身不是装饰器,返回的是装饰器 @decoratorfunc(args) <=>@decorator
    • 参数化装饰器:通过装饰器工厂函数的方式,将参数转给装饰器,典型的就是Flask的路由
    • 类装饰器
    • 叠放装饰器

    普通装饰器

    装饰器函数decorator定义的时候参数必须是一个函数。
    普通装饰器使用@decorator语法糖装饰函数时是不带参数的,它会默认将被装饰的函数作为它的参数进行传递

    def decorator(func):
        def inner(*args,**kwargs):
            "在被装饰函数之前要做的事"
            ret = func(*args,**kwargs)
            "在被装饰函数之后要做的事"
            return ret
        return inner
    
    @decorator
    def myfunc(*args,**kwargs):
        print('im myfunc')
    

    解释器会将@decorator装饰过程解释成下面这样的语句:

    myfunc = decorator(myfunc)  # 此时myfunc->inner
    

    也就是说就算我没有调用myfunc函数,就已经首先调用了decorator函数了。

    带参数的装饰器

    当我们看到@decoratorfunc(arg1,arg2)这种带有参数的装饰器的时候,我们首先意识到decoratorfunc一定不是装饰器函数,而是装饰器工厂函数,它返回的是装饰器。这样子通过装饰器工厂函数将参数传递给内部进行处理。通常带参数的装饰器的定义会是三层函数嵌套,第一层返回装饰器,第二层返回被装饰函数,第三层根据需求返回或者不返回。

    叠放装饰器

    @decorator_one
    @decorator_two
    def func():
        pass
    

    相当于:

    func = decorator_one(decorator_two(func))
    

    类装饰器

    类装饰器一般都实现了__call__方法,请看下例

    # 定义一个Time类
    class Time(object):
        def __init__(self,func): # 注意这里要设定参数接收Test
            self._func = func
        def __call__(self):
            print('我可以对函数进行装饰')
            self._func()
    
    @Time # 等价于 Test = Time(Test),此时Test是一个实例对象
    def Test():
        print('测试一下')
    
    Test()  # 调用__call__方法
    
    #结果为:
    #我可以对函数进行装饰
    #测试一下
    

    装饰器引发的问题

    使用装饰器会覆盖被装饰函数的一些属性,例如 __name____doc__,因此可以使用@warps(func)装饰器来恢复被装饰函数myfunc的相关属性

    from functiontools import wraps
    def decorator(func):
        @wraps(func)
        def inner(*args,**kwargs):
            "在被装饰函数之前要做的事"
            ret = func(*args,**kwargs)
            "在被装饰函数之后要做的事"
            return ret
        return inner
    
    @decorator
    def myfunc(*args,**kwargs):
        print('im myfunc')
    
    print(myfunc__name__)  # myfunc
    

    装饰器的案例

    1.注册回调函数

    下面这个示例展示了通过URL的路由来调用相关注册的函数示例:

    class MyApp():
        def __init__(self):
            self.func_map = {}
     
        def register(self, name):
            def func_wrapper(func):
                self.func_map[name] = func
                return func
            return func_wrapper
     
        def call_method(self, name=None):
            func = self.func_map.get(name, None)
            if func is None:
                raise Exception("No function registered against - " + str(name))
            return func()
     
    app = MyApp()
     
    @app.register('/')
    def main_page_func():
        return "This is the main page."
     
    @app.register('/next_page')
    def next_page_func():
        return "This is the next page."
     
    print app.call_method('/')
    print app.call_method('/next_page')
    

    注意:
    1)上面这个示例中,用类的实例来做decorator。
    2)decorator类中没有__call__(),但是wrapper返回了原函数。所以,原函数没有发生任何变化。

    2.线程异步

    下面量个非常简单的异步执行的decorator,注意,异步处理并不简单,下面只是一个示例。

    from threading import Thread
    from functools import wraps
     
    def async(func):
        @wraps(func)
        def async_func(*args, **kwargs):
            func_hl = Thread(target = func, args = args, kwargs = kwargs)
            func_hl.start()
            return func_hl
     
        return async_func
     
    if __name__ == '__main__':
        from time import sleep
     
        @async
        def print_somedata():
            print 'starting print_somedata'
            sleep(2)
            print 'print_somedata: 2 sec passed'
            sleep(2)
            print 'print_somedata: 2 sec passed'
            sleep(2)
            print 'finished print_somedata'
     
        def main():
            print_somedata()
            print 'back in main'
            print_somedata()
            print 'back in main'
     
        main()
    

    3.给函数调用做缓存

    from functools import wraps
    def memo(fn):
        cache = {}
        miss = object()
     
        @wraps(fn)
        def wrapper(*args):
            result = cache.get(args, miss)
            if result is miss:
                result = fn(*args)
                cache[args] = result
            return result
     
        return wrapper
     
    @memo
    def fib(n):
        if n < 2:
            return n
        return fib(n - 1) + fib(n - 2)
    

    上面这个例子中,是一个斐波拉契数例的递归算法。我们知道,这个递归是相当没有效率的,因为会重复调用。比如:我们要计算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分解成fib(2)+fib(1)…… 你可看到,基本上来说,fib(3), fib(2), fib(1)在整个递归过程中被调用了两次。

    而我们用decorator,在调用函数前查询一下缓存,如果没有才调用了,有了就从缓存中返回值。一下子,这个递归从二叉树式的递归成了线性的递归。

    相关文章

      网友评论

          本文标题:闭包和装饰器

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