• Python 可迭代对象、迭代器、生成器


    可迭代对象

    定义

    在Python的任意对象中,只要它定义了可以返回一个迭代器的 __iter__ 魔法方法,或者定义了可以支持下标索引的 __getitem__ 方法,那么它就是一个可迭代对象,通俗的说就是可以通过 for 循环遍历了。Python 原生的列表,字符串,元祖,字典等都是可以迭代的,可以通过 dir() 函数查看,如以下查看字符串,可见其定义了 __getitem__ 方法

    判断一个对象是否是可迭代对象

    方法一:isinstance + Iterable

    方法二:hasattr + __getitem__ 

    1. from collections import Iterable
    2. # 方法一
    3. print(isinstance([], Iterable)) # 返回 True,说明是可迭代对象
    4. # 方法二
    5. print(hasattr({}, '__getitem__')) # 返回 True,说明是可迭代对象

    自定义可迭代对象

    1. class Employee:
    2. def __init__(self, employee):
    3. self.employee = employee
    4. emp = Employee(['zs', 'ls', 'ww'])
    5. # 正常情况下会报错,emp 不是一个可迭代对象
    6. # TypeError: 'Employee' object is not iterable
    7. for i in emp:
    8. print(i)

     实现 __iter__ 和 __getitem__ 方法中的一个或者两个

    1. class Employee:
    2. def __init__(self, employee):
    3. self.employee = employee
    4. def __getitem__(self, item):
    5. # item 是解释器帮我们维护的索引值
    6. # 在 for 循环中,自动从 0 开始计算
    7. print(item)
    8. return self.employee[item]
    9. emp = Employee(['zs', 'ls', 'ww'])
    10. # 正常情况下会报错,emp 不是一个可迭代对象
    11. # TypeError: 'Employee' object is not iterable
    12. for i in emp:
    13. print(i)

    迭代器

    迭代器对象

    迭代器就是同时实现了 __next__ 和 __iter__ 方法(缺一不可)的对象。其中 __iter__ 方法返回迭代器自身, __next__ 方法不断返回迭代器中的下一个值,直到容器中没有更多的元素时,抛出 StopIteration 异常,以终止迭代 由定义可以知道,迭代器一定是可迭代对象,因为迭代器实现了 __iter__ 方法,满足了可迭代对象的定义。但是可迭代对象不一定是迭代器,因为不一定实现了 __next__ 方法。

    判断一个对象是迭代器

    1. from collections import Iterator
    2. print(isinstance(counter, Iterator)) # True
    3. print(isinstance([], Iterator)) # False

    为什么有了可迭代对象,还要有迭代器?

    因为迭代器采用了工厂模式,节约了内存空间。所谓的工厂模式,就是在需要的时候,才会去生产数据,而不是像可迭代对象那样一次性 全部把数据生产出来。可迭代对象如列表,字典等,会事先把所有的数据生产并保存起来,而迭代器则是在每次获取下一个值的时候 才会返回值,所以迭代器没有长度这一说法,没有长度这一属性,如果获取完了会抛出 StopIteration 异常来表示没有数据了

    所以迭代器适用于那种无限序列,不会占用很大的内存空间

    1. from itertools import count
    2. # count 是一个迭代器
    3. # 创建一个从 10 开始的无限序列
    4. counter = count(start=10)
    5. print(type(counter)) #
    6. print(dir(counter)) # 有 __iter__ 和 __next__ 方法
    7. print(next(counter)) # 10
    8. print(next(counter)) # 11
    9. # 报错,没有长度属性
    10. # TypeError: object of type 'itertools.count' has no len()
    11. # print(len(counter))

    可迭代对象转为迭代器

    1. a = [1, 2, 3, 4]
    2. print(type(a)) #
    3. a_iter = iter(a)
    4. print(type(a_iter)) #

    迭代器的特点

    1. a = [1, 2, 3, 4]
    2. a_iter = iter(a)
    3. # 每一次获取迭代器中的值之后,都是把该值从迭代器中拿出来,即迭代器中已经没有该值了,因为已经被拿出来了
    4. # 所以遍历完一次迭代器之后,不能遍历第二次
    5. # 因为遍历第一次的时候已经把值拿出来完了,第二次去遍历的时候迭代器中啥也没有了
    6. # 即迭代器不走回头路,可迭代对象则没有这种特点
    7. for i in a_iter:
    8. print(i)
    9. for i in a_iter:
    10. print(i)
    11. # 上面代码只会输出一次 1 2 3 4
    12. # 前面说的,当遍历完迭代器的最后一个数据时,再获取数据抛出异常,
    13. # 而 for 循环没有抛出的原因是
    14. # for 循环内部也是用 next() 方法获取迭代器中的值,
    15. # 当抛出异常后 for 循环会对异常进行处理,所以我们采用 for 循环遍历迭代器时不会有异常抛出

    生成器

    生成器其实是一种特殊的迭代器,但是这种迭代器更加优雅,因为不需要再像普通迭代器那样定义 __next__ 和 __iter__ 方法,只需要一个 yield 关键字,就会自动在内部帮我们实现这两个方法。所以,如果一个函数包含一个或多个 yield 关键字, 这个函数就会变为一个生成器。因为生成器是特殊的迭代器,所以它具备迭代器具备的特性

    1. def demo():
    2. print('hello')
    3. yield 5
    4. print('world')
    5. print(type(demo())) #
    6. print(dir(demo)) # 有 __iter__ 和 __next__ 方法

    yield 关键字的作用

    1、程序每次在代码中遇到 yield 关键字后,会返回结果,相当于 return 5,但是并没有真的退出程序,而是保留当前函数的运行状态

    2、返回结果后,保留当前函数的运行状态,等待下一次调用,下次调用时,从上一次返回结果处开始执行 

    第二个作用非常重要,这意味着程序控制权的转移是临时和自愿的,函数将来还会收回控制权(在下次调用生成器的时候收回), 这也是 yield 和 return 最大的区别。return 意味着函数彻底交出控制权并结束运行,下一次调用将固定从函数的第一行代码开始执行。

    1. def demo():
    2. print('hello')
    3. yield 5
    4. print('world')
    5. # 此时运行demo(),相当于调用函数,但是并不像普通函数那样马上执行,可以看控制台没有输出
    6. # 此处只是生成一个生成器对象
    7. c = demo()
    8. # 利用 next 方法调用生成器
    9. # 第一次调用,打印 hello ,并返回 5
    10. print(next(c))
    11. # 第二次调用,打印 world ,并抛出异常 StopIteration(迭代器特性)
    12. # 因为已经没有语句可以执行了,相当于数据已经取完了
    13. print(next(c))

    通过 send 向生成器传递数据

    send 方法作用:

    1、像 next 方法一样去调用生成器(调用生成器的两种方法:next 方法和 send 方法)

    2、send 方法在调用生成器时,可以同时给生成器传递数据到生成器内部 

    1. def demo():
    2. print('hello')
    3. # 注意,此处不是把 yield 5 赋值给变量 t
    4. # yield 5 是返回给调用者的值,即返回给 next(c),
    5. # 所以执行 print(next(c)) 语句时会在输出 hello 后输出 5
    6. # 而变量 t 是接收下一次调用(c.send('test'))时 send 方法传入的 test
    7. # 即 t = 'test'
    8. t = yield 5
    9. print('world')
    10. print(t)
    11. c = demo()
    12. # 第一次调用生成器,得到 t = yield 5 等号左边的表达式结果,即得到 5
    13. print(next(c))
    14. # 第二次调用,从 t = yield 5 语句开始执行,通过 send 方法把 test 传递给 t = yield 5 等号右边的变量 t
    15. # 然后接着执行函数中两个打印语句,输出 world 和 test
    16. # 打印之后就会抛出异常 StopIteration,因为已经没有要执行的语句了
    17. c.send('test')

    生成器的预激活机制

    1. def demo():
    2. print('hello')
    3. t = yield 5
    4. print('world')
    5. print(t)
    6. c = demo()
    7. # next(c) 就是生成器的预激活机制,即第一次调用
    8. # 第一次调用也可以通过 send 方法,但是参数必须为 None
    9. # 因为生成器没有办法在没有激活的情况下接收一个参数
    10. # print(next(c)) # 预激活方式一
    11. c.send(None) # 预激活方式二
    12. c.send('test')

    查看生成器的运行过程

    将下面代码的每一行打上断点(如图),进入debug模式可以清晰的看到代码的运行过程。

    1. def countdown(n):
    2. print('counting down from ', n) # 只在第一次调用生成器的时候输出,此时 n = 10
    3. while n >= 0:
    4. # 记住,这里不是将 yield n 赋值给变量 newvalue
    5. # newvalue 是用来接收 send 方法传递过来的参数的
    6. # yield n 是用来将 n 返回给调用者(next 方法或 send 方法)的
    7. newvalue = yield n
    8. # 判断是否用 send 方法传递参数,如果调用 next 方法,则传递过来的是 None
    9. if newvalue is not None:
    10. n = newvalue
    11. else:
    12. n -= 1
    13. # 获得生成器对象 c,把 10 传递给 n
    14. c = countdown(10)
    15. for i in c:
    16. # 第一次调用生成器
    17. # 第一次执行 for i in c 语句时,内部第一次调用 next(c)
    18. # 执行 print('counting down from ', n) 语句,此时 n=10
    19. # 然后进入 while n >= 0 循环,执行 newvalue = yield n 等号右边的 yield n,即返回 n=10 给i
    20. # 所以第一次for循环 i=10,输出 10
    21. # 第三次调用生成器
    22. # 第二次执行 for i in c 语句时,内部第二次调用 next(c),此时已经第三次调用生成器,第二次是用 send 方法调用的
    23. # 接着第二次调用生成器的地方开始执行,即从 newvalue = yield n 开始执行,next 方法没有给生成器传递参数
    24. # 所以 newvalue 为 None,则执行 else 语句:n -= 1,所以 n = 2
    25. # 进入 while n >= 0 ,然后返回newvalue = yield n 等号右边的 yield n,即返回 n=2
    26. # 所以第二次for循环 i=2,输出 2
    27. # 第四次调用生成器
    28. # 第三次执行 for i in c 语句时,内部第三次调用 next(c)
    29. # 接着第三次调用生成器的地方开始执行,即从 newvalue = yield n 开始执行,next 方法没有给生成器传递参数
    30. # 所以 newvalue 为 None,则执行 else 语句:n -= 1,所以 n = 1
    31. # 进入 while n >= 0 ,然后返回newvalue = yield n 等号右边的 yield n,即返回 n=1
    32. # 所以第三次for循环 i=1,输出 1
    33. # 第五次调用生成器
    34. # 第四次执行 for i in c 语句时,内部第四次调用 next(c)
    35. # 接着第四次调用生成器的地方开始执行,即从 newvalue = yield n 开始执行,next 方法没有给生成器传递参数
    36. # 所以 newvalue 为 None,则执行 else 语句:n -= 1,所以 n = 0
    37. # 进入 while n >= 0 ,然后返回newvalue = yield n 等号右边的 yield n,即返回 n=0
    38. # 所以第四次for循环 i=0,输出 0
    39. # 第六次调用生成器,最后一次
    40. # 第五次执行 for i in c 语句时,内部第五次调用 next(c)
    41. # 接着第五次调用生成器的地方开始执行,即从 newvalue = yield n 开始执行,next 方法没有给生成器传递参数
    42. # 所以 newvalue 为 None,则执行 else 语句:n -= 1,所以 n = -1
    43. # 不能进入 while n >= 0 ,那么生成器运行到最后结束,抛出一个异常 StopIteration
    44. # 抛出的异常被for循环内部处理,所以运行到此结束,for循环也结束
    45. print(i) # 分别输出 10 2 1 0
    46. # 第一次进入 for 循环时 i = 10,所以进入 if 判断
    47. if i == 10:
    48. # 第二次调用生成器
    49. # 调用 send 方法调用生成器,接着第一次调用的地方继续执行
    50. # 把参数传递给 newvalue = yield n 中的 newvalue,此时 newvalue=3
    51. # newvalue 不为 None,进入判断 if newvalue is not None,将 n 设置为 newvalue (n = newvalue)
    52. # 此时 n = 3,进入 while n >= 0 ,然后返回newvalue = yield n 等号右边的 yield n,即返回 n=3
    53. # 然后进行第二次 for 循环
    54. print('send: ', c.send(3)) # 打印 send: 3

    获得生成器的第二种方式

    除了通过 yield 关键字得到生成器外,还可以通过小括号的形式得到,如下

    1. """
    2. 生成器表达式
    3. 和列表推导式的区别:
    4. 列表推导式会一下子将所有的数据生产出来,并放到列表中
    5. 生成器表达式一次只生产一个数据
    6. 获得生成器的两种方式:
    7. 1、将普通函数里的 return 替换成 yield ,这样调用函数时会得到一个生成器
    8. 2、利用生成器表达式
    9. """
    10. # 可以通过元祖表达式(小括号)得到生成器
    11. a = (i for i in range(5))
    12. # 列表推导式得到的是一个列表
    13. b = [i for i in range(5)]
    14. # a 是生成器
    15. print(type(a)) #
    16. # b 是列表
    17. print(type(b)) #
    18. # 利用推导式获得生成器的方式称为生成器表达式
    19. t = (i * 2 for i in range(5))
    20. print(t) # at 0x000001F466254620>
    21. print(next(t)) # 0
    22. print(next(t)) # 2
    23. # 把剩下的数据处理,目前只剩下 2 3 4
    24. for i in t:
    25. print(i) # 4 6 8

    生成器实现斐波那契数列 

    1. # 用生成器实现斐波那契数列
    2. def fib():
    3. num1, num2 = 0, 1
    4. while True:
    5. yield num1
    6. num1, num2 = num2, num1 + num2
    7. f = fib()
    8. # 因为 fib 函数里是死循环,所以只要调用 next(f) 就可以一直得到 斐波那契数列的值
    9. print(next(f)) # 0
    10. print(next(f)) # 1
    11. print(next(f)) # 1
    12. print(next(f)) # 2
    13. print(next(f)) # 3
    14. print(next(f)) # 5
  • 相关阅读:
    算法通关村-----海量数据的处理方法
    DDL语句
    Ubuntu下cmake使用入门
    智慧用电监控装置:引领0.4kV安全用电新时代
    会计学基础重点
    Vsftp安装配置(超详细版)
    C#开发的OpenRA游戏之电力系统之一
    【算法|动态规划No30】leetcode5. 最长回文子串
    Linux at任务调度机制
    【21天算法挑战赛】排序算法——冒泡排序
  • 原文地址:https://blog.csdn.net/2301_77659011/article/details/132845984