第七章 - 函数装饰起和闭包

装饰器和闭包

函数装饰器用于在源码中“标记”函数, 以某种方式增强函数的行为。 这是一项强大的功能,但是若想掌握,必须理解闭包。

nonlocal是新近出现的保留关键字,在Python3.0中引入。

出了在装饰器中有用处之外,闭包还是回调式异步编程和函数式编程风格的基础。

《设计模式: 可复用面向对象软件的基础》一书是这样概述“装饰器”模式的:“动态地给一个对象添加一些额外的职责。”

 

7.1 装饰器基础知识

示例7-1-1

def decorate(func):
    def wrapper(*args, **kwargs):
        print("running inner()")
        func(*args, **kwargs)
    return wrapper


def target():
    print("running target()")

target = decorate(target)
target()

示例7-2-2, 与上面的代码功能一样

def decorate(func):
    def wrapper(*args, **kwargs):
        print("running inner()")
        func(*args, **kwargs)
    return wrapper

@decorate
def target():
    print("running target()")

target()

综上所述,装饰器几大特性:

1、可以把被装饰函数替换为其他函数

2、可以为被装饰起函数增加功能

3、装饰器在加载模块时立即执行

 

7.2 Python何时执行装饰器

示例7-2:

def decorate(func):
    print("running decorate()")
    def wrapper(*args, **kwargs):
        print("running inner()")
        func(*args, **kwargs)
    return wrapper

@decorate
def target():
    print("running target()")

if __name__ == '__main__':
    print("running main")

>>>
running decorate()
running main

示例7-2主要强调, 函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。 这突出了Python程序员所的导入时和运行时之间的区别。

 

7.3 使用装饰器改进“策略”模式

promos = []

def promotion(promo_func):
    """函数装饰器, 可以用在Django Admin中实现某些类的注册装饰器"""
    promos.append(promo_func)
    return promo_func

@promotion
def fidelity(order):
    """积分在1000或以上,折扣5%"""
    discount = 0
    if order.customer.fidenlity >= 1000:
        discount = order.total * 0.05
    return discount

@promotion
def bulk_item(order):
    """单个商品数量在20个或以上,折扣10%"""
    discount = 0
    for item in order.cart:
        if item.quantity >= 20:
            discount += item.total() * 0.1
    return discount

@promotion
def large_order(order):
    """订单中超过10个或以上不同商品,折扣7%"""
    discount = 0
    distinct_items = {item.product for item in order.cart}
    if len(distinct_items) >= 10:
        discount = order.total() * 0.07
    return discount

def best_promotion(order):
    """选择最佳折扣"""
    return max(promo(order) for promo in promos)

"""
该方案与第六章的方案相比,有以下几个有点:
1、promotions中可以有其他非promotion函数
2、@promotion装饰器突出了被装饰函数的作用,还便于临时取消某个促销策略:只需要把装饰器注释
3、促销折扣策略可以在其他模块中定义,在系统中的任何地方都可以,只要使用@promotion装饰即可。
"""

 

7.4 变量作用域规则

示例7-4-1

b = 6

def f1(a):
    print(a)
    print(b)

f1(3)
>>>
3
6

示例7-4-2

b = 6

def f1(a):
    print(a)
    print(b)  # 之所以会报错,是因为下面对b进行了赋值操作,导致在f1函数内部被当成了一个局部变量,因此跟全局变量b冲突了!
    b = 9

f1(3)
>>>
UnboundLocalError: local variable 'b' referenced before assignment

示例7-4-3

b = 6

def f1(a):
    global b    # 这里对b做了一个全局变量的申明
    print(a)
    print(b)
    b = 9

f1(3)
>>>
3
6

 

7.5 闭包

闭包是指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量,主要功能是它能访问定义体之外定义的非全局变量。

示例7-5-1

class Averager():
    def __init__(self):
        self.series = []

    def __call__(self, *args, **kwargs):
        self.series.append(*args, **kwargs)
        total = sum(self.series)
        return total/len(self.series)

avg = Averager()
print(avg(10))
print(avg(11))
print(avg(12))
>>>
10.0
10.5
11.0

示例7-5-2

def make_averager():
    series = []

    def average(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    return average

avg = make_averager()    # 等同于 avg = average
print(avg(10))
print(avg(11))
print(avg(12))
>>>
10.0
10.5
11.0
"""
make_averager()..... return average 之间的代码为“闭包”, series 为自由变量。
"""

 

7.6 nonlocal申明

示例7-6-1

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        count += 1
        total += new_value
        return total/count
    return averager

avg = make_averager()
print(avg(10))

"""
对字符串、数字、元组等不可变类型,只能读取,不能更新。如果尝试重新绑定:
例如count = count + 1,其实会隐式创建局部变量count。这样count就不是
自由变量了,因此不会保存在闭包中。
为了解决这个问题,Python3引入了nonlocal申明。
"""

示例7-6-2

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        nonlocal count, total      # 申明为自由变量
        count += 1
        total += new_value
        return total/count
    return averager

avg = make_averager()
print(avg(10))
>>>
10.0

 

7.8 标准库中的装饰器

functools工具包

functools.wraps(): 我们在使用 Decorator 的过程中,难免会损失一些原本的功能信息,而functools.wraps 则可以将原函数对象的指定属性复制给包装函数对象, 默认有 __module____name____doc__,或者通过参数选择。

import functools

def decorate(func):
    print("running decorate()")
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("running inner()")
        func(*args, **kwargs)
    return wrapper

@decorate
def target():
    print("running target()")

if __name__ == '__main__':
    print("running main")
    print(target.__name__)
>>>

running decorate()
running main
target

 

functools.lru_cache():Least Recently Used, 它是非常实用的装饰器,它实现了备忘(memoization)功能。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。

import time

def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)

if __name__ == '__main__':
    time_start = time.perf_counter()
    print(fibonacci(55))
    time_stop = time.perf_counter()
    print("time consume:", time_stop - time_start)

"""
通过递归计算斐波那契序列,花费时间.....要好几分钟
"""

通过functools.lru_cache装饰

import functools
import time

@functools.lru_cache()
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)

if __name__ == '__main__':
    time_start = time.perf_counter()
    print(fibonacci(55))
    time_stop = time.perf_counter()
    print("time consume:", time_stop - time_start)

"""
139583862445
time consume: 0.00010351205273810766    # 优化后话费时间
"""

基于生成器来实现

def fib(num):
    """基于生成器来实现"""
    n, a, b = 0, 0, 1
    while n < num:
        yield b
        a, b = b, a + b
        n += 1

 

7.8.2 单分派泛函数

functools.singledispath 这个内置装饰器函数牛逼了......单一接口调度

from functools import singledispatch

@singledispatch
def func(arg):
    print("Let me just say:", arg)

@func.register(int)
def _(arg):
    print("Int number:", arg)

@func.register(list)
def _(arg):
    for elem in arg:
        print(elem, end=" ")

func("Hello, world.")
func(1)
func([1, 2, 3, 4])

>>>
Let me just say: Hello, world.
Int number: 1
1 2 3 4 

"""
根据传递的参数的类型,调用相应的函数进行处理!
"""

 

posted @ 2017-07-24 00:18  Vincen_shen  阅读(165)  评论(0)    收藏  举报