1. 变量作用域
python的变量是有其作用域的,也就是说变量必须保存在上下文中,离开了这个上下文环境就找不到了.
1.1. LEGB原则
python的变量作用域遵循LEGB原则,即:
- L-Local(function);函数内的命名空间
- E-Enclosing function locals;外部嵌套函数的命名空间(例如closure)
- G-Global(module);函数定义所在模块(文件)的命名空间
- B-Builtin(Python);Python内置模块的命名空间
python遵循从上到下的查找方式,我们来看个例子,从闭包中观察LEGB规则.
1.2. global语句
global语句用来在函数内声明一个变量是全局变量
Pi = 3 def acreage(r): global Pi Pi = 3.14 return Pi*r**2 def perimeters(r): return Pi*r*2 def acreage1(r): Pi = 3.1 return Pi*r**2 print(perimeters(2)) print(acreage1(2)) print(acreage(2)) print(acreage1(2)) print(perimeters(2))
12 12.4 12.56 12.4 12.56
可以看出 acreage中用global声明改变了全局的Pi值,而acreage1中的pi是本地的所以只在本地有效而已.
如果要查看有哪些全局变量的话,也只需要使用内置函数globals()
即可
1.3. nolocal语句
nolocal
语句是用来声明一个变量不是本地的,它常在闭包中使用.
我们知道global
声明是明确指定一个变量作用域为模块全局,而nolocal
是声明变量在外部嵌套函数的名字空间,这样就可以在local
中修改外部嵌套函数中的变量了.
X = 1 def a(): X = 2 def b(): X = 3 print(X) return b a()()
3
X = 1 def a(): X = 2 def b(): global X X = 11 print(X) return b a()() print(X)
11 11
X = 1 def a(): X = 2 print(X) def b(): nonlocal X X = 22 print(X) return X b() print(X) return b a()()
2 22 22 22 22
1.4. 突破界限–用字典打破LEGB规则
python中字典是一个神奇的存在,它可以跨界,这主要是得益于字典是可变容器
d = {"x":1} def a(): d["x"]+=1 print(d["x"]) a() print(d["x"])
1 2
12 12.4 12.56 12.4 12.56
0
12 12.4 12.56 12.4 12.56
1
不论是global
还是nonlocal
都是LEGB
原则下高级别作用域中修改低级别作用域变量的方法.
而在python中也可以用字典来作为迂回跳开LEGB的规则限制.
1.5. 闭包
所谓闭包是指一种组织代码的结构.函数的对象也是有作用域的,我们希望一个函数可以不依赖于外界的函数或者变量,自己就可以实现它的既定功能(也就是没有副作用),那么,有的时候我们就需要在函数的内部定义函数,这就是闭包了.
在博客圈,人们有时会把闭包和匿名函数弄混.这是有历史原因的:
在函数内部定义函数不常见,直到开始使用匿名函数才会这样做.而且,只有涉及嵌套函数时才有闭包问题.因此,很多人是同时知道这两个概念的. 其实,闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量.函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量. 这个概念难以掌握,最好通过示例理解
假如有个名为
avg
的函数,它的作用是计算不断增加的系列值的均值;例如,整个历史中某个商品的平均收盘价.每天都会增加新价格,因此平均值要考虑至目前为止所有的价格.起初avg
是这样使用的:
12 12.4 12.56 12.4 12.56
2
12 12.4 12.56 12.4 12.56
3
12 12.4 12.56 12.4 12.56
4
12 12.4 12.56 12.4 12.56
5
12 12.4 12.56 12.4 12.56
6
12 12.4 12.56 12.4 12.56
7
12 12.4 12.56 12.4 12.56
8
12 12.4 12.56 12.4 12.56
9
如果使用闭包可以这样实现
X = 1 def a(): X = 2 def b(): X = 3 print(X) return b a()()
0
X = 1 def a(): X = 2 def b(): X = 3 print(X) return b a()()
1
12 12.4 12.56 12.4 12.56
4
12 12.4 12.56 12.4 12.56
5
12 12.4 12.56 12.4 12.56
6
12 12.4 12.56 12.4 12.56
7
注意,这两个示例有共通之处:调用Averager()
或make_averager()
得到一个可调用对象avg
,它会更新历史值,然后计算当前均值.不管怎样,我们都只需调用 avg(n),把 n 放入系列值中, 然后重新计算均值.
Averager
类的实例avg
在哪里存储历史值很明显:self.series
实例属性. 但是第二个示例中的avg
函数在哪里寻找series
呢?
注意,series
是 make_averager
函数的局部变量,因为那个函数的定义体中初始化了series:series = []
。可是,调用 avg(10)
时,make_averager 函数已经返回了,而它的本地作用域也一去不复返了
在 averager
函数中,series 是自由变量(free variable).这是一个技术术语,指未在本地作用域中绑定的变量:
审查返回的 averager
对象,我们发现 Python 在 __code__
属性(表示编译后的函数定义体)中保存局部变量和自由变量的名称
X = 1 def a(): X = 2 def b(): X = 3 print(X) return b a()()
6
X = 1 def a(): X = 2 def b(): X = 3 print(X) return b a()()
7
X = 1 def a(): X = 2 def b(): X = 3 print(X) return b a()()
8
X = 1 def a(): X = 2 def b(): X = 3 print(X) return b a()()
9
series的绑定在返回的avg
函数的__closure__
属性中.avg.__closure__
中的各个元素对应于avg.__code__.co_freevars
中的一个名称.这些元素是cell对象,有个cell_ contents
属性保存着真正的值.
X = 1 def a(): X = 2 def b(): X = 3 print(X) return b a()()
8
X = 1 def a(): X = 2 def b(): X = 3 print(X) return b a()()
9
3
2
3
3
3
4
3
5
综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定.
注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量
1.6. 闭包生成器
我们想输出一个包含不同参数方法的列表
3
6
3
7
3
8
看到结果都是9是不是觉得很诡异,其实这就是因为函数f要寻找变量i,在函数内部找不到i,那就会在外部嵌套函数中寻找,外部嵌套中i已经从1走到3了,也就是i=3了,那就都是为啥结果都是9了
3
9
X = 1 def a(): X = 2 def b(): global X X = 11 print(X) return b a()() print(X)
0
X = 1 def a(): X = 2 def b(): global X X = 11 print(X) return b a()() print(X)
1
这是为啥呢?其实是因为生成器是一步一步执行的,不进行next程序就没跑完,所以当我们跑main2的时候实际上i在每一步都不一样
还没有评论,来说两句吧...