当前位置:   article > 正文

python 迭代器与生成器

python 迭代器与生成器

迭代器

迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束,迭代器只能往前不会后退。

可迭代对象

我们已经知道可以对list、tuple、str等类型的数据使用 for...in... 的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。

    

 

判断一个东西是否可以迭代,导入 Iterable,当是Iterable的子类时,则可以迭代。使用isinstance()方法

 

那类对象是否可以迭代,创建一个 demo 测试一下

和数字类型一样运行报错   'Classmate' object is not iterable         

 

 

那如果想让一个类对象可以被迭代呢?我们只要在类里实现 __iter__方法即可

此时程序依然报错,但是报错内容为,iter() 方法返回 None 

 

我们使用 Iterable 验证一下,结果很明显,说明实现了 iter方法的类对象是 可迭代的对象

           

         

 

此时我们解决 Iter() 方法 返回值报错问题:

当使用for 循环迭代一个类对象时,

1.首先判断该对象是否可以被迭代(即,对象的类中是否实现了__Iter__方法

2.继续判断 __Iter__方法是否返回一个迭代器(返回一个类对象,实现该对象的类中必须有__Iter__和 __next__ 方法 )

3.然后调用iter函数(不是__iter__方法),得到迭代器(对象),继续调用next函数,此函数会调用迭代器的__next__方法,

   __next__方法return的值,就是     for temp in 对象 的 temp值 ,每for循环一次,调用一次__next__方法

 

使用 Iterator 验证 classmate_iterator 对象,返回True,说明 对象的一个迭代器

                    运行结果:

 

使用next方法迭代对应的迭代器对象,即返回迭代器 __next__方法的返回值

                  运行结果:

 

上述验证使用的init()方法和next()方法都会被for循环调用,此时我们为了将可迭代对象的对应属性传递到迭代器,

在__init__方法中传递self

                运行结果:

 

最后我们优化上述代码,将可迭代对象和迭代器放在同一个类中,即同时实现__iter__和__next__方法

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. class Classmate(object):
  4. def __init__(self):
  5. self.names = list()
  6. self.current_num = 0 # 计数器
  7. def add(self, name):
  8. self.names.append(name)
  9. def __iter__(self):
  10. """可迭代的对象,即可以使用 for,必须实现 Iter() 方法"""
  11. return self
  12. def __next__(self):
  13. if self.current_num < len(self.names): # 防止越界
  14. ret = self.names[self.current_num]
  15. self.current_num += 1
  16. return ret
  17. # 当if条件不成立时,默认继续返回None值,使用StopIteration 结束迭代
  18. else:
  19. raise StopIteration
  20. classmate = Classmate()
  21. classmate.add("老王")
  22. classmate.add("王二")
  23. classmate.add("张三")
  24. for name in classmate:
  25. print(name)

 

如果一个对象是迭代器,它一定是可迭代,对应的,如果一个对象是可迭代的,那它不一定是迭代器。

 

迭代器的作用:

下面两种代码均可实现得到前10位的斐波那契数列

而更推荐使用第二种迭代器,因为代码占用内存更小,保存的是生成的方式,需要时在使用,而不是保存生成的结果

  1. nums = list()
  2. a = 0
  3. b = 1
  4. i = 0
  5. while i < 10:
  6. nums.append(a)
  7. a, b = b, a+b
  8. i += 1
  9. for num in nums:
  10. print(num)
  1. class Fibonacci(object):
  2. def __init__(self, all_num):
  3. self.all_num = all_num
  4. self.current_num = 0
  5. self.a = 0
  6. self.b = 1
  7. def __iter__(self):
  8. return self
  9. def __next__(self):
  10. if self.current_num < self.all_num:
  11. ret = self.a
  12. self.a, self.b = self.b, self.a+self.b
  13. self.current_num += 1
  14. return ret
  15. else:
  16. raise StopIteration
  17. fibo = Fibonacci(10)
  18. for num in fibo:
  19. print(num)

 

我们进行数据类型转换时,也是利用迭代器的过程

生成一个空列表,然后进行迭代打印输出

 

 

生成器

特殊的迭代器(不需要iter next方法)

创建生成器

使用列表推导式生成一个列表,如下图

将中括号换成小括号,nums为一个对象,这就是生成器

此时我们就可以迭代nums

两者比较,一种是返回数据,一种是返回生成数据的方式,即节省空间。

 

第二种创建生成器的方式(常用)

定义一个函数,当 yield 存在时, 函数则为 生成器

如下例生成前三位的斐波那契数列数列,函数中存在 yield,此时 函数为一个生成器的模板,调用函数的返回值 obj 是一个 生成器。

运行过程:

使用for循环迭代 obj ,循环时,从上往下依次执行,当执行到 yield 语句是,将 yield 后的 a 的值赋予 num,执行暂停,打印输出 num,结束第一次循环,第二次循环,直接从 yield语句下方执行,当再次碰到 yield语句时,重复上述过程,直到运行结束

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. def create_num(all_num):
  4. print("----1---")
  5. # a = 0
  6. # b = 1
  7. a, b = 0, 1
  8. current_num = 0
  9. while current_num < all_num:
  10. print("----2---")
  11. # print(a)
  12. yield a # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
  13. print("----3---")
  14. a, b = b, a+b
  15. current_num += 1
  16. print("----4---")
  17. # 如果在调用create_num的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象
  18. obj = create_num(3)
  19. for num in obj:
  20. print(num)

运行结果:

 

当生成器模板有 return 返回值时,需要在结束异常时,使用value 得到 返回值

  1. def create_num(all_num):
  2. # a = 0
  3. # b = 1
  4. a, b = 0, 1
  5. current_num = 0
  6. while current_num < all_num:
  7. # print(a)
  8. yield a # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
  9. a, b = b, a+b
  10. current_num += 1
  11. return "ok...."
  12. obj = create_num(5)
  13. while True:
  14. try:
  15. ret = next(obj)
  16. print(ret)
  17. except Exception as ret: # 捕获异常
  18. print(ret.value) # 对应 return 的值
  19. break

 

我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行,使用send()函数的一个好处是可以唤醒的同时向断点处传入一个附加数据。

send()方法yield a 的值对应 send()方法传递的参数 

               结果:  

不可以将send放在第一次,程序会报错,因为程序从最上面执行,没有值接收传递的参数

 

总结一下,迭代器的一大特点,可以用极小的空间、代码生成想要的数据

生成器的一大特点,yield --------- 可以让一个函数暂停执行,并且每次调用的值依旧保存,下次调用可以继续使用

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/762049
推荐阅读
相关标签
  

闽ICP备14008679号