装饰器基础
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。
装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时 (即 Python 加载模块时)
变量作用域规则
对比代码
b = 3
def func(a): print(a) print(b)
func(3)
|
b = 3
def func(a): print(a) print(b) b = 9
func(3)
|
这不是缺陷,而是设计选择:Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。
如果在函数中赋值时想让解释器把 b 当成全局变量,要使用 global 声明:
b = 3
def func(a): global b print(a) print(b) b = 9
func(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
- [ ] 类装饰器