流畅的python学习记录——第七章:函数装饰器和闭包

7.1 简介

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。

7.2 无参数函数装饰器

def debug(func):
    def wrapper():
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func()
    return wrapper

def say_hello():
    print("hello!")

print(say_hello)     # 返回原始函数对象
print(say_hello())   # 调用原始函数对象, Python之函数或方法无return的返回值时,会返回None
print(debug(say_hello))   # 返回装饰器函数对象
print(debug(say_hello)())   # 调用装饰器函数对象
<function say_hello at 0x0000017305D25318>
hello!
None
<function debug.<locals>.wrapper at 0x0000017304F8F678>
[DEBUG]: enter say_hello()
hello!
None
# @语法糖
def debug(func):
    def wrapper():
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func()
    return wrapper

@debug
def say_hello():
    print("hello!")

print(say_hello)     # 返回装饰器函数对象
print(say_hello())   # 调用装饰器函数对象
<function debug.<locals>.wrapper at 0x00000173050F3C18>
[DEBUG]: enter say_hello()
hello!
None

7.3 带参数函数装饰器

  • 最笨的办法就是指定装饰器函数wrapper接受和原函数一样的参数
  • 但是装饰器要装饰的函数参数并不都一致,因此可以利用可变参数*args和关键字参数**kwargs用与装饰任意目标函数了。
# 指定装饰器函数wrapper接受和原函数一样的参数
def debug(func):
    def wrapper(something):  # 指定一毛一样的参数
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func(something)
    return wrapper  # 返回包装过函数

@debug
def say(something):
    print("hello {}!".format(something))

print(say('nick'))
[DEBUG]: enter say()
hello nick!
None
# 可变参数和关键字参数
def debug(func):
    def wrapper(*args, **kwargs):
        print("[DEBUG]: enter {}()".format(func.__name__))
        print('Prepare and say...')
        return func(*args, **kwargs)
    return wrapper

@debug
def say(something):
    print("hello {}!".format(something))

print(say('nick'))
[DEBUG]: enter say()
Prepare and say...
hello nick!
None

7.4 闭包

  • 如果在一个函数内部,嵌套了函数,这个内部函数对(非全局作用域)外部作用域的变量进行引用,那么这个内部函数称为闭包。
  • 闭包每次运行是能记住引用的外部作用域的变量的值,即闭包外层的参数可以在内存中进行保留。

假定实现一个avg函数,其作用是计算不断增加的系列值的均值;

# 使用python类
class Averager():
    def __init__(self):
        self.series = []
    def __call__(self, new_value):
        self.series.append(new_value)
        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
# 函数式实现
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(10))
print(avg(11))
print(avg(12))
10.0
10.5
11.0
  • 对于类实现来说,Averager 类的实例 avgself.series里存储历史值;
  • 对于第二种函数式实现来说, 用于存储历史值的seriesmake_averager 函数的局部变量, 其相对于averager 函数是自由变量(指未在本地作用域中绑定的变量)

7.5 nonlocal声明

# 函数式实现make_average函数,不存储所有历史值,仅保留数值之和和数目
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))
print(avg(11))
print(avg(12))
---------------------------------------------------------------------------

UnboundLocalError                         Traceback (most recent call last)

<ipython-input-15-14d7b95ec1dc> in <module>()
     10 
     11 avg = make_averager()
---> 12 print(avg(10))
     13 print(avg(11))
     14 print(avg(12))


<ipython-input-15-14d7b95ec1dc> in averager(new_value)
      4     total = 0
      5     def averager(new_value):
----> 6         count += 1
      7         total += new_value
      8         return total / count


UnboundLocalError: local variable 'count' referenced before assignment
  • 上面7.4中的例子没遇到这个问题, 因为我们没有给 series 赋值, 我们只是调用 series.append, 并把它传给 sum 和 len,因为列表是可变的对象。
  • 但是对数字、 字符串、 元组等不可变类型来说, 只能读取, 不能更新。如果尝试重新绑定, 例如 count = count + 1, 其实会隐式创建局部变量 count。 这样, count 就不是自由变量了, 因此不会保存在闭包中。

**为了上述问题, Python3 引入了 nonlocal 声明。 它的作用是把变量标记为自由变量, 即使在函数中为变量赋予新值了, 也会变成自由变量。 如果为 nonlocal 声明的变量赋予新值, 闭包中保存的绑定会更新。 **

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))
print(avg(11))
print(avg(12))
10.0
10.5
11.0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值