Effective Python 闭包、迭代器和生成器
主要内容
- LEGB Scopes和local、nonlocal、global变量
- 闭包closure
- 可迭代对象、迭代器和生成器
1 LEGB Scopes
python中变量的作用域分为4种,分别是Local、Enclosing、Global和Built-in。Python在解析引用时,按照LEGB的顺序从内向外查找,如果这四个地方都没有定义过名称相符的变量,就抛出NameError异常。
- Local:当前函数的作用域;
- Enclosing:任何外围作用域,比如包含当前函数的其它函数;
- Global:包含当前代码的那个模块的作用域;
- Built-in:内置作用域,也就是包含了len及str等函数的那个作用域。
Inner functions, also known as nested functions, are functions that you define inside other functions. In Python, this kind of function has direct access to variables and names defined in the enclosing function. Inner functions have many uses, most notably as closure factories and decorator functions.
在函数A内部定义了函数B,我们称函数B为inner function或者nested function,函数A为enclosing function。
def A():
a = 3
def B():
b = 6
- 对于变量b来说,它的Local作用域是函数B的作用域,它的Enclosing作用域是函数A的作用域;
- 对于变量a来说,它的Local作用域是函数A的作用域。
根据变量的作用域,可以将变量分成:
- local variabel:Local作用域的变量;
- non-local variabel:用nonlocal关键字标识,如果在inner function内给该变量赋值,那么修改的其实是enclosing scope中的变量;
- global variable:用global关键字表示,如果在某个函数内给改变量复制,那么会直接修改模块作用域(global scope)里那个变量。
2 闭包closure
参考资料:Using Enclosing Scopes as Closures
a closure is an inner or nested function that carries information about its enclosing scope, even though this scope has completed its execution.
闭包是带有enclosing scope状态信息的nested function。举个例子来展示闭包的使用:
enclosing function power_factory(exp)相当于一个闭包工厂函数,不同的参数exp会返回不同的power函数。闭包不仅仅是一个函数,它还带有enclosing scope的状态信息。
再写一个不断读取数据计算平均值的例子:
3 可迭代对象、迭代器和生成器
- 可迭代对象:它们可直接作用于for循环,通过函数isinstance(x, Iterable)可判断x是否为可迭代对象,像list、tuple、set、dictionary和string都是可迭代对象。可迭代对象通过函数iter()就可以得到一个迭代器。
- 迭代器:迭代器是这样一个对象,它包含了可数个值,通过函数isinstance(x, Iterator)可判断x是否为迭代器。更严格地说,迭代器是实现了迭代器协议中约定的
__iter__()
方法和__next__()
方法的对象,其中__iter__()
方法必须返回迭代器本身,__next__()
方法必须返回序列中的下个元素。
- 生成器:使用yield取代return,并返回一个lazy iterator的函数。举个《Effective Python》中16条(考虑用生成器来改写直接返回列表的函数)的例子
输入一个字符串,返回字符串中每个词的首字母所在的位置,常规写法为:
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text):
if letter == ' ':
result.append(index + 1)
return result
当text不是非常大时,这样的处理问题不大。当text非常大,比如包括上千万个单词时,程序有可能耗尽内存并崩溃,如果使用生成器改写这个函数,则可以应对任意长度的输入。
def index_words_iter(text):
if text:
yield 0
for index, letter in enumerate(text):
if letter == ' ':
yield index + 1
定义这种生成器函数时,唯一需要留意的是:函数返回的那个迭代器,是有状态的,调用者不应该反复使用它。