迭代器
在python中列表(list)、元组(tuple)、字典(dict)、集合(set)这些数据容器都有一个共同的特性,它们都支持使用for循环遍历容器中存储的元素,都是可迭代的也成为迭代器。
迭代时访问集合元素的一种非常强大的方式。迭代器可以记住遍历位置,不会对例如列表这样的数据容器一次性全部生成,而是等到用的时候才会去生成。我们也可以创建一个可迭代的对象:只要此对象含有__iter__方法,那么它就是一个可迭代的对象
如果要自定义实现一个迭代器,则类中必须实现以下两个方法:
- __next(self):返回容器的下一个元素
- __iter(self):该方法返回一个迭代器(iterator)。
这两个方法是迭代器最基本的方法,一个用来获得迭代器对象,一个用来获取容器中的下一个元素。
通过iter()方法获得了list的迭代器对象,然后就可以通过next()方法来访问list中的元素了。当容器中没有可访问的元素后,next()方法将会抛出一个StopIteration异常终止迭代器。
其实,当我们使用for语句的时候,for语句就会自动的通过__iter__()方法来获得迭代器对象,并且通过next()方法来获取下一个元素。
如下实例,自定义一个简易的列表容器迭代器
class Test:
def __init__(self):
self.__date=[] #==> list_t = []
self.__step = 0
def __next__(self):
if self.__step <= 0:
raise StopIteration
self.__step -= 1
#返回下一个元素
return self.__date[self.__step]
def __iter__(self):
#实例对象本身就是迭代器对象,因此直接返回 self 即可
return self
#添加元素
def __setitem__(self,key,value):
self.__date.insert(key,value)
self.__step += 1
t_list = Test()
t_list[0]='jibu'
t_list[1]='zhangsan'
for i in t_list:
print (i)
结果
zhangsan
jibu
python 内置的iter()函数也会返回一个迭代器
# 将列表转换为迭代器
test = iter([1, 2])
# 依次获取迭代器的下一个元素
print(test.__next__())
print(test.__next__())
print(test.__next__())
结果
Traceback (most recent call last):
File "tests.py", line 1059, in <module>
print(test.__next__())
StopIteration
当迭代完存储的所有元素之后,如果继续迭代,则 next() 方法会抛出 StopIteration 异常。
迭代器的核心功能就是通过__next__方法调用来返回下一个值。而这个值不是从已有数据中读取的,而是通过程序按照一定规则生成的。这样我们可以不再依赖一个现存的数据集来存放数据而是边用边生成,这样的好处就是可以节省大量内存空间
综上
- 迭代是Python最强大的功能之一,是访问集合元素的一种方式。
- 迭代器是一个可以记住遍历的位置的对象。
- 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
- 迭代器有两个基本的方法:iter() 和 next()。
- 字符串,列表或元组对象都可用于创建迭代器:
- iter() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。
- next() 方法(Python 2 里是 next())会返回下一个迭代器对象。
- StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。
生成器
以 list 容器为例,在使用该容器迭代一组数据时,必须事先将所有数据存储到容器中,才能开始迭代;而生成器却不同,它可以实现在迭代的同时生成元素。
- 在 Python 中,使用了 yield 的函数被称为生成器(generator)yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。
- 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
- 在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
- 调用一个生成器函数,返回的是一个迭代器对象。
生成器的创建方式,分为以下 2 步:
- 定义一个以 yield 关键字标识返回值的函数;
- 调用刚刚创建的函数,即可创建一个生成器。
def test():
for i in range(5):
yield i
print('keep on ')
result = test()
函数的返回值用的是 yield 关键字,而不是 return 关键字,此类函数又成为生成器函数。
和 return 相比,yield 除了可以返回相应的值,还有一个更重要的功能,即每当程序执行完该语句时,程序就会暂停执行。不仅如此,即便调用生成器函数,Python 解释器也不会执行函数中的代码,它只会返回一个生成器(对象)。
要想使生成器函数得以执行,或者想使执行完 yield 语句立即暂停的程序得以继续执行,有以下 2 种方式:
- 通过生成器(上面程序中的 num)调用 next() 内置函数或者 next() 方法;
- 通过 for 循环遍历生成器。
def test():
print('start')
for i in range(5):
yield i
print('keep on ')
result = test()
#调用 next() 内置函数
print(next(result))
#调用 __next__() 方法
print(result.__next__())
#通过for循环遍历生成器
for i in result:
print(i)
结果
start
0
keep on
1
keep on
2
keep on
3
keep on
4
keep on
执行流程:
- 在调用next()内置函数时,python解释器开始执行test()生成器中的代码,因此会输出开始执行,程序会执行到第一个yield i,此时 i=0,因此python解释器会输出0,因为yield的影响,程序在此处暂停
- result生成器调用__next__() 方法,该方法作用和next()函数完全相同,事实上next()函数底层执行的也是__next__() 方法。它会使程序继续执行,即输出keep on执行到yield i 此时i=1,此时程序暂停
- 在for循环遍历result生成器,for循环底层会不断调用next()函数,使暂停的程序继续执行,因此会输出后续的结果
相比迭代器,生成器最明显的优势就是节省内存空间,即它不会一次性生成所有的数据,而是什么时候需要,什么时候生成。
要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是next()。如同迭代器一样,我们可以使用next()函数来获取下一个值。
生成器和迭代器的区别
- 迭代器是一个更抽象的概念,任何对象,如果它的类有 next 方法和 iter 方法返回自己本身,对于 string、list、dict、tuple 等这类容器对象,使用 for 循环遍历是很方便的。在后台 for 语句对容器对象调用 iter()函数,iter()是 python 的内置函数。iter()会返回一个定义了 next()方法的迭代器对象,它在容器中逐个访问容器内元素,next()也是 python 的内置函数。在没有后续元素时,next()会抛出一个 StopIteration 异常。
- 生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用 yield 语句。每次 next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)
区别:生成器能做到迭代器能做的所有事,而且因为自动创建了 iter()和 next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。