Python3 迭代器和生成器
想要搞明白什么是迭代器,首先要了解几个名词:容器(container)、迭代(iteration)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)。
看图是不是更清楚点呢......
一 容器(container)
容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in
, not in
关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象)在Python中,常见的容器对象有:
- list, deque, ….
- set, frozensets, ….
- dict, defaultdict, OrderedDict, Counter, ….
- tuple, namedtuple, …
- str
容器比较容易理解,因为你就可以把它看作是一个盒子、一栋房子、一个柜子,里面可以塞任何东西。从技术角度来说,当它可以用来询问某个元素是否包含在其中时,那么这个对象就可以认为是一个容器,比如 list,set,tuples都是容器对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | print ( 1 in [ 1 , 2 , 3 ]) # lists # True print ( 4 not in [ 1 , 2 , 3 ]) # True print ( 1 in { 1 , 2 , 3 }) # sets # True print ( 4 not in { 1 , 2 , 3 }) # True print ( 1 in ( 1 , 2 , 3 )) # tuples # True print ( 4 not in ( 1 , 2 , 3 )) # True # 询问某元素是否在dict中用dict的中key: d = { 1 : 'foo' , 2 : 'bar' , 3 : 'qux' } print ( 1 in d) # True print ( 'foo' not in d) # 'foo' 不是dict中的元素 # True # 询问某substring是否在string中: s = 'foobar' print ( 'b' in s) # True print ( 'x' not in s) # True print ( 'foo' in s) # True |
尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力,当然并不是所有的容器都是可迭代的,比如:Bloom filter,虽然Bloom filter可以用来检测某个元素是否包含在容器中,但是并不能从容器中获取其中的每一个值,因为Bloom filter压根就没把元素存储在容器中,而是通过一个散列函数映射成一个值保存在数组中。
二 迭代(iteration)
什么是迭代,我的理解如下:
-
第一,迭代需要重复进行某一操作
-
第二,本次迭代的要依赖上一次的结果继续往下做,如果中途有任何停顿,都不能算是迭代
下面来看几个例子,能更好理解迭代的含义。
1 2 3 4 5 6 7 8 9 10 11 12 13 | # 实例1 # 非迭代 count = 0 while count < 10 : print ( "hello world" ) count + = 1 # 实例2 # 迭代 count = 0 while count < 10 : print (count) count + = 1 |
实例1,仅仅只是在重复一件事,那就是不停的打印"hello world",并且,这个打印的结果并不依赖上一次输出的值。而实例2,就很好地说明迭代的含义,重复+继续。
三 可迭代对象 (iterable)
通俗的说就是在每一种数据类型对象中,都会有有一个__iter__()方法,正是因为这个方法,才使得这些基本数据类型变为可迭代。
当我们运行以下代码的时候:
1 2 3 4 5 6 7 8 | x = [ 1 , 2 , 3 ] for elem in x: print (elem) # 运行结果: # 1 # 2 # 3 |
实际调用过程如下:
那么如何判断一个对象是否是可迭代呢?使用collections模块的Iterable类型判断
1 2 3 4 5 6 7 8 | from collections import Iterable print ( isinstance ( 'abc' , Iterable)) # str是否可迭代 # True print ( isinstance ([ 1 , 2 , 3 ], Iterable)) # list是否可迭代 # True print ( isinstance ( 123 , Iterable)) # 整数是否可迭代 # False |
四 迭代器(iterator)
通俗来讲任何具有__next__()
方法的对象都是迭代器,对迭代器调用__next__()方法可以获取下一个值。
五 生成器(generator)
生成器是一个用简单的方式来完成迭代。简单来说,Python的生成器是一个返回可以迭代对象的函数。
那要怎么创建生成器呢,很简单的,在一般函数中使用yield
关键字,可以实现一个最简单的生成器,此时这个函数变成一个生成器函数。yield
与return
返回相同的值,区别在于return
返回后,函数状态终止,而yield
会保存当前函数的执行状态,在返回后,函数又回到之前保存的状态继续执行。
看一下简单的生成器实例吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 | def test(): yield 1 yield 2 yield 3 g = test() print ( '来自函数' ,g) print (g.__next__()) print (g.__next__()) # 运行结果 # 来自函数 <generator object test at 0x000000000072B8E0> # 1 # 2 |
生成器与一般函数有什么区别呢?
- 生成器函数包含一个或者多个
yield
- 当调用生成器函数时,函数将返回一个对象,但是不会立刻向下执行
- 像
__iter__()
和__next__()
方法等是自动实现的,所以我们可以通过next()
方法对对象进行迭代 - 一旦函数被
yield
,函数会暂停,控制权返回调用者 - 局部变量和它们的状态会被保存,直到下一次调用
- 函数终止的时候,
StopIteraion
会被自动抛出
来个例子看一下吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | # 简单的生成器函数 def my_gen(): n = 1 print ( "first" ) # yield区域 yield n n + = 1 print ( "second" ) yield n n + = 1 print ( "third" ) yield n a = my_gen() print ( "next method:" ) # 每次调用a的时候,函数都从之前保存的状态执行 print print ( next (a))( next (a)) print ( next (a)) # 运行结果 # next method: # first # 1 # second # 2 # third # 3 print ( "for loop:" ) # 与调用next等价的 b = my_gen() for elem in my_gen(): print (elem) # 运行结果 # for loop: # first # 1 # second # 2 # third # 3 |
来看看使用循环的生成器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # 逆序yield出对象的元素 def rev_str(my_str): length = len (my_str) for i in range (length - 1 , - 1 , - 1 ): yield my_str[i] for char in rev_str( "hello" ): print (char) # 运行结果 # o # l # l # e # h |
六 生成器表达式
Python中,有一个列表生成方法,也就是常说的列表解析,提到列表解析就先要弄明白三元表达式的概念,什么是三元表达式呢?来个实例看看吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | egg_list = [] for i in range ( 10 ): egg_list.append( '鸡蛋%s' % i) print (egg_list) # ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9'] # 使用三元表达式替换如上代码 l = [ '鸡蛋%s' % i for i in range ( 10 )] print (l) # ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9'] l1 = [ '鸡蛋%s' % i for i in range ( 10 ) if i > 5 ] print (l1) # ['鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9'] # l2=['鸡蛋%s' %i for i in range(10) if i > 5 else i] #没有四元表达式 # print(l2) l3 = [ '鸡蛋%s' % i for i in range ( 10 ) if i < 5 ] print (l3) # ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4'] |
了解了三元表达式,我们再来看看什么是生成器表达式,其实很简单,就是把三元表达式中的[]换成()即可。
1 2 3 4 5 6 7 8 9 10 11 12 | a = (x for x in range ( 10 )) b = [x for x in range ( 10 )] # 这是错误的,因为生成器不能直接给出长度 # print("length a:",len(a)) # 输出列表的长度 print ( "length b:" , len (b)) # length b: 10 b = iter (b) # 二者输出等价,不过b是在运行时开辟内存,而a是直接开辟内存 print ( next (a)) print ( next (b)) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!