python迭代器和生成器

  • 迭代是访问集合元素的一种方式。
  • 迭代器是一个表示数据流的对象,它可以记住遍历的位置,每次返回数据的下一个元素。
  • 迭代器从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

1. 容器、可迭代对象

在python中一切皆对象,对象的抽象就是类,而对象的集合就是容器。

列表:[, ]
元组:(, )
字典: {0:0, }
集合 ( )

​以上都是容器。对于容器,可以想象成多个元素在一起的单元;而不同容器的区别,正是内部数据结构的实现方法。然后就可以针对不同场景,选择不同时间和空间复杂度的容器。

所有的容器都是可迭代的(iterable)。这里的迭代,和枚举不完全一样。

迭代可以想象成是去买苹果,卖家并不告诉你他又多少库存。这样每次你都需要告诉卖家,你要一个苹果,然后卖家采取行为:要么给你一个苹果;要么告诉你,苹果已经卖完了。你并不需要知道,卖家在仓库是怎么摆放苹果的。

如何判断一个对象是否可迭代。(还有另一种方法, 是 isinstance(obj. lterable))。

def is_iterable(param):
    try: 
        iter(param) 
        return True
    except TypeError:
        return False

params = [
    1234,
    '1234',
    [1, 2, 3, 4],
    set([1, 2, 3, 4]),
    {1:1, 2:2, 3:3, 4:4},
    (1, 2, 3, 4)
]
    
for param in params:
    print('{} is iterable? {}'.format(param, is_iterable(param)))

########## 输出 ##########

1234 is iterable? False
1234 is iterable? True
[1, 2, 3, 4] is iterable? True
{1, 2, 3, 4} is iterable? True
{1: 1, 2: 2, 3: 3, 4: 4} is iterable? True
(1, 2, 3, 4) is iterable? True

2. 可迭代对象的本质

我们分析对可迭代对象进行迭代的过程,发现每迭代一次(即在for...in...中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。
那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为迭代器(Iterator)

可迭代对象的本质就是可以向我们提供一个这样的中间“人”即迭代器帮助我们对其进行迭代遍历使用。

迭代器帮助我们进行数据的迭代,而可迭代对象,通过__iter__ 方法向我们提供一个迭代器,再通过__next__方法就可以实现遍历。for in 语句将这个过程隐式化,所以只需要知道大概做了什么就行了。

一个具备了 _iter_ 方法的对象就是一个可迭代对象

>>> class MyList(object):
...     def __init__(self):
...             self.container = []
...     def add(self, item):
...             self.container.append(item)
...     def __iter__(self):
...             """返回一个迭代器"""
...             # 暂时忽略如何构造一个迭代器对象
...             pass
...
>>> mylist = MyList()
>>> from collections import Iterable
>>> isinstance(mylist, Iterable)
True
>>>
# 这次发现添加了__iter__方法的mylist对象已经是一个可迭代对象了

3.iter() 函数和 next() 函数

list、tuple等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。iter()函数实际上就是调用了可迭代对象的__iter__方法。

>>> li = [11, 22, 33]
>>> li_iter = iter(li)
>>> next(li_iter)
11
>>> next(li_iter)
22
>>> next(li_iter)
33
>>> next(li_iter)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

4.迭代器

通过上面的分析,迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的__next__方法(Python3中是对象的__next__方法,Python2中是对象的next()方法)。所以,我们要想构造一个迭代器,就要实现它的__next__方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。

一个实现了__iter__方法和__next__方法的对象,就是迭代器。

class MyList(object):
    """自定义的一个可迭代对象"""
    def __init__(self):
        self.items = []

    def add(self, val):
        self.items.append(val)

    def __iter__(self):
        myiterator = MyIterator(self)
        return myiterator


class MyIterator(object):
    """自定义的供上面可迭代对象使用的一个迭代器"""
    def __init__(self, mylist):
        self.mylist = mylist
        # current用来记录当前访问到的位置
        self.current = 0

    def __next__(self):
        if self.current < len(self.mylist.items):
            item = self.mylist.items[self.current]
            self.current += 1
            return item
        else:
            raise StopIteration

    def __iter__(self):
        return self


if __name__ == '__main__':
    mylist = MyList()
    mylist.add(1)
    mylist.add(2)
    mylist.add(3)
    mylist.add(4)
    mylist.add(5)
    for num in mylist:
        print(num)

5.生成器

生成器是懒人版本的迭代器
利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。
但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。
常规的函数计算一个值并返回,但是生成器会返回返回值的迭代器。

声明一个迭代器很简单,[i for i in range(100000000)] 就可以生成一个包含一个一亿元素的列表,每个元素生成后都会保存在内存中,不过不需要在内存中同时保存这么多东西,比如对元素求和,只需要知道在相加那一刻是多少就行了,用完就可以释放了。

于是 就出现了生成器,在你调用 __next__ 方法是 才会生成下一个变量,生成器在python的写法使用小括号括起来, (i for i in range(10000000)) ,即初始化了一个生成器

生成器被认为是可恢复执行的函数。

def generate_ints(N):
    for i in range(N):
        yield i

任何含有yield关键字的函数都被认为是生成器函数。

当生成器函数被调用时,它不会返回单个值,而是会返回支持迭代器协议的生成器对象。
在执行yield语句时,生成器输出i的值,这和return语句类似。yield和return语句之间的最大区别在于,在到达yield时,生成器的执行状态将被暂停,并保留局部变量。在下一次调用生成器的.next()方法时,该函数将继续执行。
下面是generate_ints()生成器的使用方法:

>>> gen = generate_ints(3)
>>> gen
<generator object generate_ints at 0x10dfe2a50>
>>> gen.next()
0
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

写成for i in generate_ints(3)或者a,b,c=generate_ints(3)也是一样的。

posted @ 2020-04-30 10:35  乐乙  阅读(238)  评论(0)    收藏  举报