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"))

迭代器和生成器异同

在这里插入图片描述

  • 生成器是特殊的迭代器。
  • 生成器对比迭代器在编码方面更加简洁。
  • 生成器运行速度更快。
  • 最重要的一点:生成器节省内存。

如果我们使用其他可迭代对象处理庞大的数据时,当创建或者返回值时会申请用于存储整个可迭代对象的内存,显然这是非常浪费的,因为有的元素当前我们用不到,也不会去访问,但它却一直占用这内存。这时候就体现了生成器的优点,它不是一次性把所有的结果都返回,而是当我们每读取一次,它会返回一个结果,当我们不读取时,它就是一个生成器表达式,几乎不占用内存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值