Python闭包与装饰器总结1

装饰器基础

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

装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时 (即 Python 加载模块时)

变量作用域规则

对比代码

b = 3

def func(a):
print(a)
print(b)

func(3) # 3 3
b = 3

def func(a):
print(a)
print(b)
b = 9

func(3) # 3 UnboundLocalError

这不是缺陷,而是设计选择:Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。

如果在函数中赋值时想让解释器把 b 当成全局变量,要使用 global 声明:

b = 3

def func(a):
global b
print(a)
print(b)
b = 9

func(3) # 3 3

闭包

闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。

假如有个名为 avg 的函数,它的作用是计算不断增加的系列值的均值。

>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

那么它的实现,可以是类的形式:

class Average:
def __init__(self) -> None:
self.sum = 0
self.count = 0
pass

def __call__(self, num:int) -> Any:
self.sum += num
self.count += 1
return self.sum / self.count

avg = Average()

print(avg(10))
print(avg(11))
print(avg(12))

也可以是高阶函数

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

avg = make_averager()

print(avg(10))
print(avg(11))
print(avg(12))def make_averager():
total = 0
count = 0
def averager(num: int):
nonlocal total, count
total += num
count += 1
return total / count
return averager

avg = make_averager()

print(avg(10))
print(avg(11))
print(avg(12))

注意nonlocal total, count​:

因为在Python中并没有要求先声明一个变量,所以Python解释器任务在函数体内,只要对一个变量进行赋值操作,那么这个变量就是局部变量。而 count+=1相当于 count=count+1,对 count 进行了赋值操作,所以Python解释器认为 count 是函数内的局部变量。我们这里需要用nonlocal关键字将局部变量修正为自由变量。

所谓自由变量就是没有被绑定在局部作用域的变量。

装饰器

利用闭包实现日志装饰器:对于被装饰函数,每次调用打印调度log

def logit(func):

def with_logging(*args, **kwargs):
print(func.__name__, "was called")
return func(*args, **kwargs)
return with_logging

@logit
def some_func(x):
"""do something"""
return x + x

print(some_func(5))

@符号是一个语法糖,实际的执行是:logit(some_func(x))

如果装饰器需要带参数,(例如下面的代码实现了将日志保存到指定文件),则需要再加一层:

def logit(log_file = 'out.log'):
def _logit(func):
def with_logging(*args, **kwargs):
print(func.__name__, "was called")
print(f'save log to {log_file}')
return func(*args, **kwargs)
return with_logging
return _logit

@logit(log_file='a.log')
def some_func(x):
"""do something"""
return x + x

print(some_func(5))

Ref

《流畅的Python》

More

关于装饰器更多内容还包括:

  • [ ] @functools.wraps
  • [ ] @lru_cache()和@singledispatch
  • [ ] 类装饰器
文章作者: Met Guo
文章链接: https://guoyujian.github.io/2024/03/06/Python%E9%97%AD%E5%8C%85%E4%B8%8E%E8%A3%85%E9%A5%B0%E5%99%A8%E6%80%BB%E7%BB%931/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Gmet's Blog