(第六篇)__iter__、__next__及for循环执行原理(可迭代对象、迭代器、生成器)

发布时间 2023-04-05 16:34:16作者: hechengQAQ

摘要:只要有__iter__,那么这个对象就是可迭代对象,若对象有__iter__和__next__两种方法,则这个对象为迭代器对象。

一、概念

什么是迭代?

迭代就是重复,但是每一次重复都与上一次有关联,这就是迭代。

"""
这不是迭代,这是简单的重复
"""

while True:
    print(1)

"""
这是迭代。每一次重复与上一次的值有关
"""
num = 0
while True:
    num +=1

迭代器的出现是为了解决什么问题?

"""
Python迭代器是为了解决遍历数据集合的问题而来的。在Python中,可以使用for循环遍历列表、元组、字典等数据集合,但是对于大型数据集合或者需要进行复杂的数据处理时,这种方法可能会导致内存占用过大或者处理速度过慢的问题。
迭代器提供了一种延迟计算数据的方法,只有当需要使用数据时,才会进行计算并返回结果。这种方式可以有效减少内存占用,并且可以提高代码的执行效率。

"""

什么是迭代器?

"""
代器是一个可以记住遍历位置的对象,它从集合的第一个元素开始访问,直到所有元素被访问完毕。迭代器只能向前不会后退。由__iter__、__next__实现
"""

二、迭代器示例

python设计迭代器的原因:是为了寻求一种不依赖索引也能够进行迭代取值的方案,所以python给那些没有索引的数据类型都内置了一个功能,叫__iter__功能,如果我们不想或者不能依赖索引进行迭代取值,那我们就可以调用python给我们提供的这个功能就可以了,只要调用了__iter__方法,那么python就会将迭代对象转化为迭代器对象,有了这个迭代器对象,我们就可以不依赖索引进行迭代取值了,那这个迭代器对象是怎么不依赖于索引进行迭代取值呢?

d = {"key1": 1, "key2": 2, "key3": 3}
res = d.__iter__()  # 将可迭代对象转化为迭代器 
print(res)
print(res.__next__())  # key1
print(res.__next__())  # key2
print(res.__next__())  # key3
print(res.__next__())  # 这时取值已经取完,将抛出StopIteration异常
"""
迭代器节约内存资源,把迭代器比喻成一只老母鸡,这里只能下三只蛋,下完后就死了,不能重新调用生成,如果想再次生成,就自己再新建一只老母鸡。
就是说如果这个老母鸡一辈子可以生1000个鸡蛋,肯定不是它肚子里直接就存着1000个鸡蛋,而是一个一个来的
"""

while第一次循环就已经取完了老母鸡里的鸡蛋,然后老母鸡就死了,所以第二次循环是取不到值的,直接捕获异常break了。那么我们就需要重新再造一只老母鸡

while True:
    try:
        print(res.__next__())
    except StopIteration:
        break


while True:
    try:
        print(res.__next__())
    except StopIteration:
        break

"""
其他的类型只要有__iter__方法的数据类型---列表、元组、字符串、集合、还有文件,不管有索引还是没有索引,都可以用这种方法进行迭代取值。
"""

# 针对上面使用的while循环,直接使用for循环要简单很多(与for循环原理一样)

d = {"key1": 1, "key2": 2, "key3": 3}

for key in d:
    print(key)

迭代器调用iter()方法:

print(res.__iter__())  # 打印迭代器本身
"""
即然调用iter()打印的是迭代器本身,那么这种方式没有意义,为什么要设计出来呢?真的就是没有意义吗?不,这种方式的出现是

为了让for循环的工作原理统一起来,不管for循环的in后面跟的是可迭代对象,还是迭代器对象,都可以采用同一套运行机制,如果我们for i in 一个可迭代对象,它会去调用这个可迭代对象的__iter__方法,把它转换成一个迭代器,如果这里本身放的就是一个迭代器对象,那我for循环的工作原理不变,还是会
去调用它的__iter__(迭代器对象的__iter__),拿到的结果也是一个迭代器
"""

注意点:

列表、元组、集合、字典都没有__next__方法,所以他们是可迭代对象,文件对象有__iter__、__next__,是迭代器对象

 

三、for循环原理

1.调用__iter__()方法将可迭代对象转化为迭代器对象

2.while循环不断调用迭代器对象的__next__()方法,直到所有元素都被遍历。

3.在每次调用__next__()方法时,迭代器会返回下一个元素。如果没有更多元素,next()方法会抛出StopIteration异常。

4.for循环捕获StopIteration异常,结束循环。

 

字典的values()、keys()、items()都是可迭代对象:

print({"key1":123}.items().__iter__().__next__()) # ('key1', 123),先转换成了迭代器,然后取值。实际上这是执行了for循环原理。

# 所以得出for循环可直接遍历:
for k,v in {"key1": 123}.items():
    print(k)  # key1
    print(v)  # 123

list、tuple工作原理

"""
for循环的原理就是这三个步骤,所以说list和tuple的工作原理也是这三步,先调用符串下面的__iter__方法,转成一个迭代器,然后调用迭代器下面的__next__方法拿到返回值,把返回值放在列表里面。
"""

四、迭代器与生成器区别

  • 迭代器是一种工具,它可以遍历访问可迭代对象中的所有元素,但是它并不是可迭代对象。
  • 生成器是一种特殊的迭代器,它可以通过函数来实现。生成器函数使用yield语句来返回一个值,每次调用生成器函数时,都会从上一次yield语句处继续执行。因此,生成器可以延迟计算,只有在需要时才会生成下一个元素,从而节省内存。

生成器与迭代器示例:

# 迭代器示例
class Counter:
    def __init__(self, n):
        self.n = n
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current < self.n:
            num = self.current
            self.current += 1
            return num
        else:
            raise StopIteration

# 生成器示例
def counter(n):
    current = 0
    while current < n:
        yield current
        current += 1

# 生成器表达式生成生成器
gen = (x for x in range(1, 4))
print(type(gen)) # <class 'generator'>
print(next(gen)) # Output: 1
print(next(gen)) # Output: 2
print(next(gen)) # Output: 3

计算序列中元素之和:

# 使用迭代器计算序列中所有元素的和
def iter_sum(nums):
    total = 0
    it = iter(nums)
    while True:
        try:
            total += next(it)
        except StopIteration:
            break
    return total

# 使用生成器计算序列中所有元素的和
def gen_sum(nums):
    total = 0
    for num in nums:
        total += num
    return total

综上所述,迭代器和生成器都是用来遍历访问序列中的元素的工具,它们之间的区别在于实现方式的不同。迭代器是一种工具,生成器是一种特殊的迭代器,它可以通过函数来实现,并且可以延迟。