2018.6.29 Python第十二课

继续上节课的迭代器,然后还有新的闭包和装饰器。


一、迭代器

1.什么是迭代器:
它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter__和__next__()方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常,至于它们到底是如何实现的这并不重要。
所以,迭代器就是实现了工厂模式的对象,它在你每次你询问要下一个值的时候给你返回。有很多关于迭代器的例子,比如itertools函数返回的都是迭代器对象。
2.实例:
为了更直观地感受迭代器内部的执行过程,我们自定义一个迭代器,以斐波那契数列为例:

class Fib:
    def __init__(self):
        self.prev = 0
        self.curr = 1

    def __iter__(self):
        return self

    def __next__(self):
        value = self.curr
        self.curr += self.prev
        self.prev = value
        return value
f=Fib()
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))

以上代码输出结果为:1 1 2 3 5

Fib既是一个可迭代对象(因为它实现了__iter__方法),又是一个迭代器(因为实现了__next__方法)。实例变量prevcurr用于维护迭代器内部的状态。每次调用next()方法的时候做两件事:
1.为下一次调用next()方法修改状态
2.为当前这次调用生成返回结果
迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。

3.迭代器总结:
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
4.拓展:
Python的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

#以上代码完全等价于以下代码

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 获得下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

5.区别

可迭代对象:实现了__iter__方法,该方法返回一个迭代器对象。
迭代器:持有一个内部状态的字段,用于记录下次迭代返回值,它实现了__next__和__iter__方法,迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果。
生成器:是一种特殊的迭代器,它的返回值不是通过return而是用yield。
迭代器与可迭代对象


二、闭包

1.闭包含义
闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
如果在一个内嵌函数里,对在外部函数内(但不是在全局作用域)的变量进行引用,那么内嵌函数就被认为是闭包(closure)。
定义在外部函数内但由内部函数引用或者使用的变量称为自由变量。
一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是自由变量。
总结一下,创建一个闭包必须满足以下几点:
1. 必须有一个内嵌函数
2. 内嵌函数必须引用外部函数中的变量
3. 外部函数的返回值必须是内嵌函数
2.闭包实例

def func(name):
    def in_func(age):
        print("name:%s,age:%s"%(name,age))
    return in_func
a=func("Tom")
a(15)

以上实例输出结果为name:Tom,age:15
这里当调用 func 的时候就产生了一个闭包——in_func,并且该闭包持有自由变量——name,因此这也意味着,当函数func的生命周期结束之后,name这个变量依然存在,因为它被闭包引用了,所以不会被回收。
3.闭包陷阱

def create():
    return [lambda x:i*x for i in range(5)]  #推导式生成一个匿名函数的列表
create()
for mul in create():
    print(mul(2))

以上代码输出结果为:8 8 8 8 8
结果是不是很奇怪,这算是闭包使用中的一个陷阱吧!来看看为什么?
在上面的代码当中,函数create返回一个list里面保存了4个函数变量,这4个函数都共同的引用了循环变量i, 也就是说它们共享着同一个变量i,i是会改变的,当函数调用时,循环变量i已经是等于4了,因此4个函数返回的都是8。如果,需要在闭包使用循环变量的值的话,把循环变量作为闭包的默认参数或者是通过偏函数来实现。实现的原理也很简单,就是当把循环变量当参数传入函数时,会申请新的内存。示例代码如下:

def create():
    return [lambda x,i=i:i*x for i in range(5)]
for mul in create():
    print(mul(2))

再次运行输出结果就变成了:0 2 4 6 8


4.闭包&LEGB法则

LEGB含义解释:
• Local - 本地函数内部,通过任何方式赋值的,而且没有被global关键字声明为全局变量的变量;
• Enclosing - 直接外围空间(上层函数)的本地作用域,查找变量(如果有多层嵌套,则由内而外逐层查找,直至最外层的函数);
• Global - 全局空间(模块XXX.py),在模块顶层赋值的变量;
• Builtin - 内置模块(__builtin__)中预定义的变量名中查找变量;
LEGB规定了查找一个名称的顺序为:local-->enclosing-->global-->builtin


总结:
. 闭包最重要的使用价值在于:封存函数执行的上下文环境;
. 闭包在其捕捉的执行环境(def语句块所在上下文)中,也遵循LEGB规则逐层查找,直至找到符合要求的变量,或者抛出异常。

猜你喜欢

转载自blog.csdn.net/weixin_40313627/article/details/80876644