首页 > 其他分享 >(第六篇)__iter__、__next__及for循环执行原理(可迭代对象、迭代器、生成器)

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

时间:2023-04-05 16:47:55浏览次数:31  
标签:__ 迭代 生成器 iter next print

摘要:只要有__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

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

 

标签:__,迭代,生成器,iter,next,print
From: https://www.cnblogs.com/hechengQAQ/p/17289676.html

相关文章

  • sizeof 和 strlen 的区别
    sizeof() 是一个运算符,而 strlen() 是一个函数。sizeof() 计算的是变量或类型所占用的内存字节数,而 strlen() 计算的是字符串中字符的个数。sizeof() 可以用于任何类型的数据,而 strlen() 只能用于以空字符‘0’结尾的字符串。 注意:chars[]="hello"prinrf("%......
  • uni-app:tabbar自定义中间凸起按钮的tabbar(hbuilderx 3.7.3)
    一,官方文档地址:https://uniapp.dcloud.net.cn/collocation/pages.html#tabbarhttps://uniapp.dcloud.net.cn/api/ui/tabbar.html#ontabbarmidbuttontap二,代码1,pages.json"tabBar":{"color":"#7A7E83",//字体颜色"sel......
  • SpringMVC 结合 Servlet 不成功问题集锦
    问题集锦question1:Tomcat10版本太高causeTomcat10版本..主要就是说:Tomcat10之前的用户如果使用Tomcat10就需要将javax.*的导入转变为jakarta.*。这样才能将代码在Tomcat10中部署,还给你了一个迁移工具migrationtool来帮助你迁移代码。resolve换Tomcat9......
  • list取交集并集
    >a<-c(2,3,4,5,6)>b<-c(5,6,7,8,9)>c<-c(2,2,2,3,4)>d<-c(3,6,6,6,6)>#取交集>intersect(a,b)[1]56>#取两个list中都存在的部分>union(a,b)[1]23456789>#取前面一个list中不含后面一个list中元素的部分>setdiff(a,b)[1]234>......
  • Redis——(主从复制、哨兵模式、集群)的部署及搭建
    重点:主从复制:主从复制是高可用redis的基础,主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。哨兵和集群都是在主从复制基础上实现高可用的。缺点:故障恢复无法自动化,写操作无法负载均衡,存储能力受到单机的限制。哨兵:在主从复制的基础上,哨兵......
  • 10静态路由原理及配置
    1.路由:从源主机到目标主机的转发过程(路由器就是能够将数据包转发到正确的目的地,并在转发的过程中选择最佳路径的设备就是路由器)2.根据路由表转发数据1网段要给4网段发送消息:主机1的数据首先到路由器A这里,路由器A这里首先会查询路由器表,查看自己的路由表中数据发送到4网段数......
  • 在Linux部署Etcd集群
    前言目前解决分布式系统下数据强一致性的主要算法理论是Paxos和Raft,偏向CAP定理一致性(Consistency)、可用性(Availability)、分区容错性(Partitiontolerance)中的CP。Raft在容错和性能方面和Paxos相当,不同之处在于它将问题分解成相对独立的子问题,逻辑较为清晰,更易于理解。关于Raft......
  • Spring 源码解析 - xml解析封装BeanDefinition(1)
    -  XML解析封装BeanDefinition  断点在 DefaultListableBeanFacy,registerBeanDefinition()二 如果给属性赋值 三 各种postprocessor       ##2、Spring套路点-1、AbstractBeanDefinition看看何时给容器中注入了什么组件-2、BeanFactory让......
  • MYSQL大批量数据插入的性能问题
    批处理rewriteBatchedStatements=true项目原来使用的大批量数据插入方法是Mybatis的foreach拼接SQL的方法。我发现不管改成MybatisBatch提交或者原生JDBCBatch的方法都不起作用,实际上在插入的时候仍然是一条条记录的插,速度远不如原来Mybatis的foreach拼接SQL的方法。这对于常......
  • 算法之回溯算法
    回溯法含义:类似枚举,一层一层往下递归寻找答案,尝试搜索答案,如果找到了答案,则返回答案,并且寻找其他可能的答案。如果没找到,则像上一层递归寻找可能的答案。回溯算法也是递归算法的一种。为什么要回溯呢?或者说为什么用到回溯算法呢?因为我们不是要找到一个排列就好了,而是需要找......