迭代器(iterator)
迭代器是在python2.2中被加入的,它为类序列对象提供了一个类序列的接口。有了迭代器可以迭代一个不是序列的对象,因为他表现出了序列的行为。
什么迭代器呢?
迭代器的实质是实现了next()方法的对象,常见的元组、列表、字典都是迭代器。
迭代器中重点关注两种方法:
iter方法:返回迭代器自身。可以通过python内建函数iter()调用。
next方法:当next方法被调用的时候,迭代器会返回它的下一个值,如果next方法被调用,但迭代器没有值可以返回,就会引发一个StopIteration异常。该方法可以通过 python 内建函数next()调用。
举例
内建函数iter()可以从可迭代对象中获得迭代器。
>>> it = iter([1,2,3])
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
迭代器是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter__和__next__()(python2中实现next())方法的对象都是迭代器。
__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常,至于它们到底是如何实现的这并不重要。
迭代器就是实现了工厂模式的对象,它在你每次你询问要下一个值的时候给你返回。
如何创建一个迭代器
要创建一个迭代器有2种方法,其中前两种分别是:
- 为容器对象添加 iter() 和 next() 方法(Python 2.7 中是 next());iter() 返回迭代器对象本身 self,next() 则返回每次调用 next() 或迭代时的元素;
- 内置函数 iter() 将可迭代对象转化为迭代器
案例:创建迭代器容器
# Create iterator Object
class Container:
def __init__(self, start=0, end=0):
self.start = start
self.end = end
def __iter__(self):
print("I made this iterator!")
return self
def __next__(self):
print("Calling __next__ method!")
if self.start < self.end:
i = self.start
self.start += 1
return i
else:
raise StopIteration()
c = Container(0, 5)
for i in c:
print(i)
对于迭代器对象,使用for循环遍历整个数组其实是个语法糖,他的内部实现还是通过调用对象的__next__()方法。
创建迭代器对象的好处是当序列长度很大时,可以减少内存消耗,因为每次只需要记录一个值即刻(经常看到人们介绍 Python 2.7 的 range 函数时,建议当长度太大时用 xrange 更快,在 Python 3.5 中已经去除了 xrange 只有一个类似迭代器一样的 range)。
斐波那契数列应用举例
我们如果采用正常的斐波那契数列求值过程如下:
def fibs(n):
if n == 0 or n == 1:
return 1
else:
return fibs(n-1) + fibs(n-2)
print([fibs(i) for i in range(10)])
自定义一个迭代器, 实现斐波那契数列
class Fib2(object):
def __init__(self, n):
self.a = 0
self.b = 1
self.n = n
self.count = 0
def __iter__(self):
return self
def __next__(self):
res = self.a
self.a, self.b = self.b, self.a + self.b
if self.count > self.n:
raise StopIteration
self.count += 1
return res
print(list(Fib2(10)))
其他迭代器案例
(1) 有很多关于迭代器的例子,比如itertools函数返回的都是迭代器对象。
def test_endless_iter():
'''
生成无限序列
:return:
'''
from itertools import count
counter = count(start=3)
print(next(counter))
print(next(counter))
print(next(counter))
(2) 从一个有限序列中生成无限序列
def test_cycle_iter():
'''
从一个有限序列中生成无限序列
:return:
'''
from itertools import cycle
colors = cycle(['red', 'white', 'blue'])
print(next(colors))
print(next(colors))
print(next(colors))
print(next(colors))
总结:
对于list、string、tuple、dict等这些容器对象,使用for循环遍历是很方便的。在后台for语句对容器对象调用iter()函数。iter()是python内置函数。
iter()函数会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内的元素。next()也是python内置函数。在没有后续元素时,next()会抛出一个StopIteration异常,通知for语句循环结束。
迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的next方法(Python3中是对象的next方法,Python2中是对象的next()方法)。所以,我们要想构造一个迭代器,就要实现它的next方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现iter方法,而iter方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的iter方法返回自身self即可。
生成器
生成器作用?
延迟操作。也就是在需要的时候才产生结果,不是立即产生结果。
什么是生成器?
生成器是包含yield关键字的函数。本质上来说,关键字yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。
生成器调用顺序
那么,生成器是怎么调用执行的呢?只需要了解下面几条规则即可:
a. 当生成器被调用的时候,函数体的代码不会被执行,而是会返回一个迭代器,其实,生成器函数返回生成器的迭代器。 “生成器的迭代器”这个术语通常被称作”生成器”。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是next()。如同迭代器一样,我们可以使用next()函数来获取下一个值。需要明白的是,这一切都是在yield内部实现的。
b. 当next()方法第一次被调用的时候,生成器函数才开始执行,执行到yield语句处停止
next()方法的返回值就是yield语句处的参数(yielded value)
当继续调用next()方法的时候,函数将接着上一次停止的yield语句处继续执行,并到下一个yield处停止;如果后面没有yield就抛出StopIteration异常。
c.每调用一次生成器的next()方法,就会执行生成器中的代码,知道遇到一个yield或者return语句。yield语句意味着应该生成一个值(在上面已经解释清楚)。return意味着生成器要停止执行,不在产生任何东西。
d. 生成器的编写方法和函数定义类似,只是在return的地方改为yield。生成器中可以有多个yield。当生成器遇到一个yield时,会暂停运行生成器,返回yield后面的值。当再次调用生成器的时候,会从刚才暂停的地方继续运行,直到下一个yield。生成器自身又构成一个循环器,每次循环使用一个yield返回的值。
注意:
(1)生成器是只能遍历一次的。
(2)生成器是一类特殊的迭代器。
分类:
第一类:生成器函数:
还是使用 def 定义函数,但是,使用yield而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。
如下案例加以说明:
def Fib3(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return '亲!没有数据了...'
print("@@@@@@@@@@@@@222")
# 调用方法,生成出10个数来
f=Fib(10)
# 使用一个循环捕获最后return 返回的值,保存在异常StopIteration的value中
while True:
try:
x=next(f)
print("f:",x)
except StopIteration as e:
print("生成器最后的返回值是:",e.value)
break
第二类:生成器表达式:
类似于列表推导,只不过是把一对大括号[]变换为一对小括号()。但是,生成器表达式是按需产生一个生成器结果对象,要想拿到每一个元素,就需要循环遍历。
如下案例加以说明:
# 一个列表
xiaoke=[2,3,4,5]
# 生成器generator,类似于list,但是是把[]改为()
gen=(a for a in xiaoke)
for i in gen:
print(i)
#结果是:
2
3
4
5
为什么要使用生成器?因为效率。使用生成器表达式取代列表推导式可以同时节省 cpu 和 内存(RAM)。如果你构造一个列表(list)的目的仅仅是传递给别的函数, 比如 传递给tuple()或者set(), 那就用生成器表达式替代吧!
iter
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def next(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration();
return self.a # 返回下一个值
现在,试试把Fib实例作用于for循环:
>>> for n in Fib():
... print n
...
1
1
2
3
5
...
46368
75025
getitem
要表现得像list那样按照下标取出元素,需要实现getitem()方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
现在,就可以按下标访问数列的任意一项了:
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101
但是list有个神奇的切片方法:
>>> range(100)[5:10]
[5, 6, 7, 8, 9]
对于Fib却报错。原因是getitem()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice):
start = n.start
stop = n.stop
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
现在试试Fib的切片:
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
但是没有对step参数作处理:
>>> f[:10:2]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
也没有对负数作处理,所以,要正确实现一个getitem()还是有很多工作要做的。
来源:CSDN
作者:Z_single
链接:https://blog.csdn.net/weixin_42143550/article/details/81193714