- 流畅的python学习记录——第一章:python数据模型
- 流畅的python学习记录——第二章:数据结构——序列构成的数组
- 流畅的python学习记录——第三章:字典和集合
- 流畅的python学习记录——第四章:None
- 流畅的python学习记录——第五章:一等函数
- 流畅的python学习记录——第六章:None
- 流畅的python学习记录——第七章:函数装饰器和闭包
- 流畅的python学习记录——第八章:None
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
类的实例avg
在self.series
里存储历史值; - 对于第二种函数式实现来说, 用于存储历史值的
series
是make_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