Python迭代器,可迭代对象,生成器
for循环语句的过程
我们经常看到的for in语句,超级方便,那么,在执行for循环的过程中,是怎么执行的呢?例如:
list1 = [1, 2, 3]
for i in list1:
print(i)
执行步骤如下:
- 先执行执行 iter(list1),返回一个迭代器对象。
- 每次循环时,会迭代执行一次__next__方法,返回一个值作为新的i。
- __next__方法如果没有可迭代的数据,抛出 StopIteration 异常,for循环会根据这个异常停止迭代。
迭代器
上面说到迭代器,那么问题来了,迭代器是个什么法器?
在 Python 中,实现迭代器协议就是实现以下 2 个方法:
- _ _ iter _ _:这个方法返回一个迭代器,可以简单的返回self。
- _ _ next _ _:这个方法每次返回迭代的值,在没有可迭代元素时,抛出 StopIteration 异常。
产生一个迭代器类
举个例子:
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
rev = Reverse('Jim')
rev.__next__()
next(rev)
next(rev)
rev.__next__()
result:
m
i
J
StopIteration
在执行过程中,单步运行,可以发现,不管是rev._ _ next_ _()或者是next(rev)都会进到__next__方法执行。
而如果调用系统的iter()函数,则会执行__iter__方法。
判断是否为迭代器
- 使用isinstance。
# 使用元组生成器,实际上产生一个生成器对象。
# 而生成器对象是迭代器的一种。
from collections.abc import Iterator
a1 = (i *2 for i in range(5))
print(isinstance(a1, Iterator))
result:
True
可迭代对象
- 如果一个对象具有__iter__方法(),且__iter__ 方法返回一个迭代器,那么这个对象就是可迭代对象。
- 迭代器就属于可迭代对象。
判断是否为可迭代对象
# 如果是第一个return语句,则a是个迭代器。
# 如果是第二个return语句,则a只是可迭代对象。
因为其__Iter__方法并未返回迭代器。
from collections.abc import Iterator
from collections.abc import Iterable
class Fun:
def __init__(self, *args):
self.value = args
print(self.value)
def __iter__(self):
# return (i*2 for i in self.value) # 返回迭代器
return "abcself" # 返回非迭代器
a = Fun([1,2,3])
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))
result:
([1, 2, 3],)
True
False
自定义一个可迭代对象xrange
class InterRange:
def __init__(self, num):
self.num = num
self.cnt = -1
def __iter__(self):
return self #返回自身
def __next__(self):
self.cnt += 1
if self.cnt >= self.num:
raise StopIteration
return self.cnt
class Xrange:
def __init__(self, num):
self.num = num
def __iter__(self):
return InterRange(self.num) # 返回一个迭代器对象
print([s for s in Xrange(4)])
result:
[0, 1, 2, 3]
迭代器,可迭代对象的异同
- 都具有__iter__方法,返回的都是迭代器。
- 迭代器有__next__方法,而可迭代对象没有,但是可迭代对象可以通过__iter__方法返回一个迭代器。
- __next__方法每次返回迭代器的一个元素,当迭代器内无元素时,会报StopIteration错误。
- list, string,tuple, dict,set都是可迭代对象。如果想将其转换成迭代器,可使用iter()方法。
生成器
把生成器放在这里,是因为生成器也是一种迭代器。而且高大上。
比如我想产生一个列表,有一个算法生成它,有100万个数据。但是其实访问的也就只有几个。怎么办?我需要用推导式开辟超大空间去无端浪费吗?NO,用生成器。你用一个它给你生成一个,不用不生。大大减少了浪费,毕竟浪费是可耻的。
- 生成器可以用来解决使用序列存储大量数据时,内存消耗大的问题。
- 可以根据存储数据的某些规律,演算为算法,在使用过程中动态通过计算得到,这样可以不用创建完整序列,从而大大节省占用空间。
- 同时,生成器是一个用于创建迭代器的简单而强大的工具。
创建生成器的方法
- 生成器表达式
- 生成器函数
生成器表达式
- 用语法类似列表推导式,但外层为圆括号而非方括号。
- 生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。
xvec = [10, 20, 30]
yvec = [7, 5, 3]
a = (x*y for x,y in zip(xvec, yvec))
print(a)
print(a.__next__())
result:
<generator object <genexpr> at 0x000002A5413F3A20>
70
生成器函数
- 当函数使用yield方法,则该函数就成为了一个生成器。调用该函数,就等于创建了一个生成器对象,在调用函数时,并不会执行里面的语句,只有当执行__next__方法时才会执行。
- 在要返回数据时使用 yield 语句。
- 每次在生成器上调用 next() /__next__时,它会从上次离开的位置(yield语句)恢复执行(它会记住上次执行语句时的所有数据值)
使用yield
"""执行过程:
g1 = func(3):创建了一个生成器对象。
第1个print(g1.__next__()): 开始执行func函数,执行至yield sum, 将sum的值作为g1.__next__()的返回值,程序执行到这里, 记录。
第2个print(g1.__next__()):从print("--------after yield---------")继续执行到yield sum,返回,陈旭执行到这里,记录。
依次类推。
第4个print(g1.__next__()): print(g1.__next__()):执行print("--------after yield---------")语句后到for语句,发现i已经超出范围,报StopIteration错误。
"""
def func(n):
sum = 0
print("Func start")
for i in range(n):
sum += i
yield sum
print("--------after yield---------")
g1 = func(3)
print("********next start*********")
print(g1.__next__())
print(g1.__next__())
print(g1.__next__())
print(g1.__next__())
result:
********next start*********
Func start
0
--------after yield---------
1
--------after yield---------
3
--------after yield---------
StopIteration
使用send
- 通过迭代器的send() 方法,还可以向生成器中传值。其执行过程类似于__next__方法。
"""执行过程:
1. g1 = func(3): 产生生成器对象。
2. print(g1.__next__())执行到yield sum,停止,返回,注意并未执行h=的赋值操作。
3. print(g1.send("first")), 在h=这一句把"first"的赋值给h。继续执行到yield sum,停止,返回。
4. 依次类推。
5. 注意在第一次迭代的时候,要么使用__next__方法,使得执行到yield,下一次赋值的时候,有值可赋,或者使用send方法,但是参数传None。
"""
def func(n):
sum = 0
print("Func start")
for i in range(n):
print("--------for start---------")
sum += i
h = yield sum
print(f"{h}, --------after yield---------")
g1 = func(3)
print("********send start*********")
print(g1.__next__()) #或者为print(g1.send(None)),但是不能传其它参数
print(g1.send("first"))
print(g1.send("second"))
迭代器和生成器异同
- 生成器是特殊的迭代器。
- 生成器对比迭代器在编码方面更加简洁。
- 生成器运行速度更快。
- 最重要的一点:生成器节省内存。
如果我们使用其他可迭代对象处理庞大的数据时,当创建或者返回值时会申请用于存储整个可迭代对象的内存,显然这是非常浪费的,因为有的元素当前我们用不到,也不会去访问,但它却一直占用这内存。这时候就体现了生成器的优点,它不是一次性把所有的结果都返回,而是当我们每读取一次,它会返回一个结果,当我们不读取时,它就是一个生成器表达式,几乎不占用内存。