函数装饰器和闭包

 

装饰器的作用

  函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为。

  装饰器是可调用对象,参数是另一个函数(被装饰的函数)。

  装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。

装饰器的特(1)把被装饰的函数替换成其他函数。(2)装饰器在加载模块时立即执行

变量作用域规则:Python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。

闭包:延伸了作用域的函数。包含函数定义体中引用,同时还可以访问定义体之外定义的非全局变量。

def make_average():
    series = []
    
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    
    return average

>>> avg = make_average()
>>> avg(10)

  在averager函数中,series是自由变量free variable)指未在本地作用域中绑定的变量。

  Python在 __code__ 属性中保存 局部变量自由变量的名称。

>>> avg.__code__.co_varname
('new_value','total')
>>> avg.__code__.co_freevars
('series',)

  series的绑定在返回的avg对象的__closure__属性中。

  avg.__closure__中的各个元素对应于avg.__code__.co_freevars中的一个名称。这些元素是cell对象。

  它们的值保存在cell_contents属性中。

>>> avg.__code__.co_freevars
('series',)
>>> avg.__closure__
(<cell at 0x000001C2EBE261C8: list object at 0x000001C2EBE33988>,)
>>> avg.__closure__[0].cell_contents
[10,]

  nonlocal声明:把变量标记为自由变量。

def make_averager():
    count = 0
    total = 0
    def averager(new_value):
        nonlocal count,total
        count += 1
        total += new_value
        return total/count

    return average

  当变量是数字或任何不可变类型时,averager的定义体中为count赋值,实质会隐式创建局部变量count。

  引入nonlocal声明,把变量标记为自由变量,即使在函数中为变量赋予新值,也会变成自由变量。

  

标准库中的装饰器

functools.lru_cache 实现了备忘功能。把耗时的函数结果保存起来,避免传入相同的参数时重复计算。

递归函数适合使用lru_cache。

functools.lru_cache(maxsize=128,typed=Fales)

maxsize参数指定存储多少个调用的结果。缓存满了后,旧得结果会被扔掉。maxsize应该设置为2的幂

typed参数为True,不同参数类型得到的结果分开保存。

▲ lru_cache使用字典存储结果,键根据调用时传入的定位参数和关键字参数创建,所以被装饰的函数所有参数必须是可散列的。

functools.singledispatch 装饰器可以把整体方案拆分成多个模块。

根据第一个参数的类型,以不同的方式执行相同操作的一组函数。

from functools import singledispatch
from collections import abc
import numbers

@singledispatch
def inp_check(obj):
    print('Main inp_check')

@inp_check.register(str)
def _(text):
    print('Text %s'%text)

@inp_check.register(numbers.Integral)
def _(n):
    print('Int %s' %n)

@inp_check.register(tuple)
@inp_check.register(abc.MutableSequence)
def _(seq):
    print('List %s'%seq)

注册的专门函数应该处理抽象基类(numbers.Integral和abc.MutableSequence),不需处理具体实现(int、list)。

这样,代码支持的兼容类型更广泛。

 

参数化装饰器

  Python把被装饰的函数作为第一个参数传递给装饰器函数,那么如何给装饰器传递其他参数呢?

def outer(func):
    def inner(*args,**kwargs):
        
        print('Do Something..')
        result = func(*args,**kwargs)
        return result
    
    return inner

@outer
def f1(num):
    print('Number %s'%num)

# ========= 等效于
f1 = outer(f1)

  我们可以创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它运用到要装饰的函数上。

def register(active=False):
    
    def outer(func):
        def inner(*args,**kwargs):
            print('Do Something..')
            result = func(*args,**kwargs)
            return result
        return inner
    
    return outer

@register(active=True)
def f1(num):
    print('Number %s'%num)

# =========等效于
f1 = register(active=True)(f1)

  

 

posted @ 2019-07-16 10:41  5_FireFly  阅读(219)  评论(0编辑  收藏  举报
web
counter