温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

python迭代器和生成器

发布时间:2020-06-19 07:49:34 来源:网络 阅读:3381 作者:LJ_baby 栏目:编程语言

python中的迭代器

可迭代对象

迭代是指重复反馈过程,每一次的迭代都会得到一个结果,又是下一次迭代的开始。
在python中,一个对象只要是实现了__iter__() 或__getitem__()方法,就被称为可迭代对象。

python中的可迭代对象有字符串、列表、元组、字典、文件;自定义的类若是实现了__iter__() 或__getitem__()方法,则也是可迭代对象~

遍历可迭代对象,这里仅以文件为例:

with open(file='/Users/luyi/tmp/abc', mode='r', encoding='utf-8') as f:
    for line in f:
        print(line, end='')

迭代器

调用可迭代对象的__iter__() 方法,返回得到的就是一个迭代器,迭代器用于迭代可迭代对象中的每一个元素。

迭代器有两个方法:__iter__() 方法,__next__() 方法,调用迭代器的__iter__() 方法,返回的依旧是迭代器对象,即将自身返回。不断的调用__next__() 方法,则会逐个返回可迭代对象中的元素~

with open(file='/Users/luyi/tmp/abc', mode='r', encoding='utf-8') as f:  # abc 文件中仅有3行内容
    file_iter = f.__iter__()                      # 获取迭代器对象     
    print(file_iter.__next__(), end='')    # 读取第一行内容
    print(file_iter.__next__(), end='')    # 读取第二行内容
    print(file_iter.__next__(), end='')    # 读取第三行内容
    print(file_iter.__next__(), end='')    # 对象中的元素已经全部迭代完成,所以这一行会抛出 StopIteration 异常

输出结果:
aaa
bbb
ccc
...                # 省略部分报错信息
StopIteration

如上所示,在获取可迭代对象的迭代器之后,不断调用迭代器的__next__() 方法,以遍历其中的所有元素,当全部遍历完成后,再次调用__next__() 方法,就会抛出 StopIteration 异常

遍历玩所有的元素,这样写会比较麻烦,因为需要不断的调用__next__() 方法,其实这里可以使用 for 循环替代,实现的方式在 实例1 中已经给出。

for...in... 循环的过程

for item in Iterable 循环 会调用 in 后面对象的 __iter__() 方法,得到迭代器,然后自动的,不断的 调用迭代器的__next__()方法,得到的返回值 赋值给 for 前面的item 变量,这样依次循环;直到调用__next__()方法时报错(StopIteration异常),for循环会自动捕获异常,然后循环结束~

Iterable, Iterator

Iterable, Iterator 用于判断一个对象是不是 可迭代对象或者 是不是迭代器~
使用语法如下:

from collections import Iterable, Iterator
isinstance(str1, Iterable)
isinstance(str1, Iterator)

使用示例:

from collections import Iterable, Iterator
f = open(file='/Users/luyi/tmp/abc', mode='r', encoding='utf-8')
print(isinstance(f, Iterable))     # True
print(isinstance(f, Iterator))      # True

lst = [1, 2, 3]
print(isinstance(lst, Iterable))   # True
print(isinstance(lst, Iterator))   # False

可见文件对象是可迭代对象,又是迭代器,而列表仅是可迭代对象~

迭代器的特性

总结一下,迭代器有以下 2 个特性:
1)提供了一种不依赖于索引的取值方式
2)惰性计算,节省内存

这里的节省内存是指 迭代器在迭代过程中,不会一次性把可迭代对象的所有元素都加载到内存中,仅仅是在迭代至某个元素时才加载该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点就使得迭代器适合用于遍历一些巨大的 或是 无限的集合。

迭代器的优缺点

1)取值不如按照索引取值来的方便(索引可以直接定位某一个值,迭代器不行,只能一个一个地取下去)
2)迭代器只能往后迭代,不能回退(执行__next__() 方法 只能向后,不能向前)
3)无法获取迭代器的长度

python中的生成器

生成器函数 和 生成器

生成器函数:函数体内包含有yield关键字,该函数执行的结果就是一个生成器(generator)。

>>> def foo():
...     print('first----')
...     yield 1
...     print('second----')
...     yield 2
...     print('third----')
...     yield 3
...     print('fouth----')

如上示例中 foo函数就是一个生成器函数,而执行foo()函数后,返回的就是一个生成器对象。生成器具有 __next__() 方法 和 __iter__(),所以生成器就是一种迭代器~
python迭代器和生成器

调用该生成器函数后,返回generator对象,然后通过调用 __next__() 方法不断获得下一个返回值:

>>> g = foo()                 # 返回一个生成器
>>> g
<generator object foo at 0x101fdbe08>
>>> g.__next__()           # 这里也可以使用 next(g) 来替代
first----
1
>>> g.__next__()
second----
2
>>> g.__next__()
third----
3
>>> g.__next__()
fouth----
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

在调用 生成器 的 __next__() 方法时,会执行对应生成器函数中的内容,执行过程中,每次遇到 yield 就会返回 yield 后面的变量(或者表达式),随即中断;再次调用生成器 的 __next__() 方法,会从上一次的中断处继续往后执行;而最后一次执行__next__()方法,后面已经没有 yield,在打印了 'fouth----' 之后就会抛出 StopIteration 异常~

一般很少使用 __next__() 方法来迭代生成器,而是直接使用for循环来迭代,且for循环会自动捕获 StopIteration 异常:

g = foo()
for i in g:
    print(i)

输出结果:
first----
1
second----
2
third----
3
fouth----

 
生成器函数一般也会同 for 循环配合使用,例如使用通配符匹配指定目录下的文件并打印:

def files(dest_dir = '/Users/luyi/tmp/', end = '*.log'):
    for i in glob.glob(os.path.join(dest_dir, end)):
        yield i

# files() 得到一个生成器
print(files())       # <generator object files at 0x1021d9500>

for i in files():    # 遍历这个生成器
    print i

输出结果:
/Users/luyi/tmp/2.log
/Users/luyi/tmp/3.log
/Users/luyi/tmp/1.log

 
yield功能总结:
1)与return类似,都可以返回值,但不一样的地方在于 yield 返回多次值,而 return 只能返回一次
2)为函数封装好了__iter__() 和 __next__() 方法,把函数的执行结果做成了迭代器
3)遵循迭代器的取值方式(obj.__next__()),这样的操作会触发函数的执行,函数暂停与再继续的状态都是由 yield 保存(暂停于yield处,下一次的 __next__() 方法执行后,会从 yield 处继续往下执行)

yield的表达式形式

>>> def foo():
...     print('start...')
...     while True:
...         x = yield
...         print(x)
... 
>>> g = foo()  # 第一次执行生成器,必须是 next 或者 send(None),类似于初始换的操作,即让程序执行至第一个 yield,并中断~
>>> next(g)
start...
>>> g.send(2)
2
>>> g.send(3)
3
>>> next(g)      # 没有传值的情况
None

这里生成器的 send() 方法会先将值传递给 yield,然后由 yield 赋值给 x,赋值完成之后,继续往下执行,直到再一次遇到yield。所以 send 的作用和 next 方法相同,还多了一个赋值功能。

send 的 2个作用:
1)传值给 yield,然后由 yield 传递给变量(若没有传值给 yield,则 yield 会将 None 赋值给变量)
2)与next相同的功能

上述示例中使用 send 之前,必须对生成器来一个类似于初始化的操作:执行next 或者 send(None)。为了简化这个步骤,这里可以使用装饰器:

def init(func):            # 这个装饰器可以重复使用(装饰其它生成器)
    def wrapper(*args, **kwargs):
        g = func(*args, **kwargs)
        next(g)
        return g
    return wrapper

@init
def foo():
    print('start...')
    while True:
        x = yield
        print(x)

g = foo()
g.send('abc')

yield表达式一共有4种:
1)yield exp,仅有返回值,exp可以是函数,表达式等
2)s = yield exp,有返回值,且可以传入一个值存入 s 中
3)s = yield,可传入一个值,没有返回值(返回为None)
4)yield,不接受输入,也没有返回值(返回为None)

生成器的应用

python中的生成器(generator)通常用来实现协程,即在执行一个函数的过程中,中断当前函数,转而去执行别的函数,执行完成之后,返回来继续当前函数的执行,整个过程在一个线程中完成;也可以换个角度来进行理解,即当前函数的循环执行会不断产生数据,将每一次产生的数据交由另一个函数做进一步的处理,处理完成之后返回,继续执行当前函数~

下面通过一个简单的生产者-消费者模型来说明:

def init(func):
    def wrapper(*args, **kwargs):
        g = func(*args, **kwargs)
        next(g)
        return g
    return wrapper

@init
def consumer():
    res = ''
    while True:
        p = yield res
        if not p:
            continue
        print('Consuming %s...' % p)
        res = 'OK'

def produce(c):
    for i in range(1,5):
        print('producing %s...' % i)
        r = c.send(i)
        print('return status: %s' % r)
    c.close()

c = consumer()
produce(c)

输出结果:
producing 1...
Consuming 1...
return status: OK
producing 2...
Consuming 2...
return status: OK
producing 3...
Consuming 3...
return status: OK
producing 4...
Consuming 4...
return status: OK

执行流程说明:
1.c = consumer() 拿到的已经是初始化后的生成器(即生成器已经执行了一次next(c));
2.调用 produce(),生产数据之后,通过send(i),将数据发送给 consumer,并且切换到consumer执行;
3.consumer 通过 yield 获取数据,然后进行消费,最后通过 yield 把处理结果返回给produce;
4.produce 获取 consumer 的处理结果之后,继续生产下一次的数据~

.................^_^

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI