nonlocal

装饰器,nonlocal

定义一个计算移动平均值的高阶函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
return averager

>>>avg = make_averager()
>>>avg(10)
10.0
>>>avg(20)
15.0

series是make_averager函数的局部变量,是averager函数的自由变量,其名称保存在__code__属性中,绑定的值保存在__closure__属性中:

1
2
3
4
5
6
>>>avg.__code__.co_varnames
('new_value', 'total')
>>>avg.__code__.co_freevars
('series',)
>>>avg.__closure__[0].cell_contents
[10, 20]

但是如果这样会报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def make_averager():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total / count
return averager

>>>avg = make_averager()
>>>avg(10)
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-10-ace390caaa2e> in <module>----> 1 avg(10)

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

UnboundLocalError: local variable 'count' referenced before assignment

由于列表是一个可变对象,因此在averager函数中并未给series赋值,而是调用了append方法; 而数字是一个不可变对象,此时改变count的值会隐式创建局部变量,count就不再是自由变量了

1
2
3
4
5
>>>avg.__code__.co_varnames
('new_value', 'count', 'total')

>>>avg.__code__.co_freevars
()

此时可以用nonlocal声明,将变量重新标记为自由变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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()

>>>avg(10)
10.0

>>>avg(20)
15.0

>>>avg.__code__.co_varnames
('new_value',)

>>>avg.__code__.co_freevars
('count', 'total')

装饰器中的变量的作用域也是这样的:下面定义一个简单的装饰器,在函数每次调用时打印其调用次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def decorator(func):
count = 0
def wrapper():
nonlocal count
count += 1
result = func()
print('{}函数共调用了:'.format(func),count,'次')
return result
return wrapper

@decoratordef cal():
return 10

>>>for i in range(10):
cal()

<function cal at 0x00000237C30E7168>函数共调用了:1 次<function cal at 0x00000237C30E7168>函数共调用了:2 次<function cal at 0x00000237C30E7168>函数共调用了:3 次<function cal at 0x00000237C30E7168>函数共调用了:4 次<function cal at 0x00000237C30E7168>函数共调用了:5 次<function cal at 0x00000237C30E7168>函数共调用了:6 次<function cal at 0x00000237C30E7168>函数共调用了:7 次<function cal at 0x00000237C30E7168>函数共调用了:8 次<function cal at 0x00000237C30E7168>函数共调用了:9 次<function cal at 0x00000237C30E7168>函数共调用了:10

@decoratordef cal_2():
return 100

>>>for i in range(10):
cal_2()

<function cal_2 at 0x00000237C30C0798>函数共调用了:1 次<function cal_2 at 0x00000237C30C0798>函数共调用了:2 次<function cal_2 at 0x00000237C30C0798>函数共调用了:3 次<function cal_2 at 0x00000237C30C0798>函数共调用了:4 次<function cal_2 at 0x00000237C30C0798>函数共调用了:5 次<function cal_2 at 0x00000237C30C0798>函数共调用了:6 次<function cal_2 at 0x00000237C30C0798>函数共调用了:7 次<function cal_2 at 0x00000237C30C0798>函数共调用了:8 次<function cal_2 at 0x00000237C30C0798>函数共调用了:9 次<function cal_2 at 0x00000237C30C0798>函数共调用了:10