闭包和装饰器

2018-09-04  本文已影响0人  MononokeHime

闭包

之前一直不明白闭包的定义-内层函数引用了外层函数的变量(包括它的参数)就构成了闭包。我觉得也没什么了不起的,干嘛还专门起一个别扭的名字闭包呢。后来发现,其实并不是想的这么简单,内层函数调用外层变量的时候,这时候外层函数已经调用结束返回了,理论上外层函数所有的局部变量都已经释放,所以内层函数不应该拿到外层函数的变量(包括它的参数)。闭包就是将使用到的变量(称为环境变量)绑定到内层函数的__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定义的时候参数必须是一个函数。
普通装饰器使用@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,在调用函数前查询一下缓存,如果没有才调用了,有了就从缓存中返回值。一下子,这个递归从二叉树式的递归成了线性的递归。

上一篇 下一篇

猜你喜欢

热点阅读