文章目录一、迭代器迭代器即用来迭代取值的工具,是一个可以记住遍历的位置的对象。 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。 1. 可迭代对象(Iterable)通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串,列表,元组。 对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式,这就用到了迭代器。 字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。 迭代器对象可以使用常规for语句进行遍历,也可以使用 next() 函数。 2. 迭代器对象(Iterator)迭代器对象是内置有iter和next方法的对象,打开的文件本身就是一个迭代器对象。 执行迭代器对象 .iter() 方法得到的仍然是迭代器本身。 执行迭代器 .next() 方法就会计算出迭代器中的下一个值。 3. for 循环原理有了迭代器后,便可以不依赖索引迭代取值了,使用while循环的实现方式如下: goods=['mac','lenovo','acer','dell'] i=iter(goods) #每次都需要重新获取一个迭代器对象 while True: try: print(next(i)) except StopIteration: #捕捉异常终止循环 break for循环又称为迭代循环,in后可以跟任意可迭代对象,上述while循环可以简写为: goods=['mac','lenovo','acer','dell'] for item in goods: print(item) for 循环在工作时: 首先会调用可迭代对象goods内置的iter方法拿到一个迭代器对象, 然后再调用该迭代器对象的next方法将取到的值赋给item,执行循环体完成一次循环, 周而复始,直到捕捉StopIteration(停止迭代),结束迭代。 4. 迭代器的优缺点优点 1、为序列和非序列类型提供了一种统一的迭代取值方式。 2、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值。 迭代器同一时刻在内存中只有一个值,因而可以存放无限大的数据流。 而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。 缺点 1、除非取尽,否则无法获取迭代器的长度。 2、只能取下一个值,不能回到开始。 迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next。 若是要再次迭代同个对象,只能重新调用iter方法创建一个新的迭代器对象。 如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。 二、生成器使用了 yield 的函数被称为生成器(generator)。 生成器是一个返回迭代器的函数,只能用于迭代操作,可理解为生成器就是一个自定义迭代器。 1. yield 原理yield 能够临时挂起当前函数,记下其上下文(包括局部变量、待决的 try catch 等),将控制权返回给函数调用者。 当下一次再调用其所在生成器时,会恢复保存的上下文,继续执行剩下的语句,直到再遇到 yield 或者退出为止。 在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值,并在下一次执行 next() 方法时从当前位置继续运行。 2. yield 和 return 区别yield可以用于返回值,但不同于return。 函数一旦遇到return就结束了,销毁上下文(弹出栈帧),将控制权返回给调用者。 而yield可以保存函数的运行状态,挂起函数,用来返回多次值。 因此,以 yield 进行执行流控制的函数称为生成器函数,以 return 进行执行流控制的函数就是普通函数。 由于可以临时挂起函数的执行,yield 可以充当其调用者和被挂起函数间交互的桥梁。 3. yield 表达式应用在函数内可以采用表达式形式的yield def eater(): print('Ready to eat.') while True: food = yield print('get the food: %s, and start to eat.' % food) 可以拿到函数的生成器对象持续为函数体send值,如下 >>> g=eater() # 得到生成器对象 >>> g <generator object eater at 0x101b6e2b0> >>> next(e) # 需要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值 Ready to eat >>> g.send('包子') get the food: 包子, and start to eat >>> g.send('鸡腿') get the food: 鸡腿, and start to eat 针对表达式形式的yield,生成器对象必须事先被初始化一次, 让函数挂起在food=yield的位置,等待调用g.send()方法为函数体传值。 g.send(None)等同于next(g)。 可以编写装饰器来完成为所有表达式形式 yield 对应生成器的初始化操作,如下 def init(func): def wrapper(*args,**kwargs): g = func(*args,**kwargs) next(g) return g return wrapper @init def eater(): print('Ready to eat.') while True: food = yield print('get the food: %s, and start to eat.' %food) 表达式形式的yield也可以用于返回多次值,即 变量名=yield 值 的形式,如下 >>> def eater(): ... print('Ready to eat') ... food_list = [] ... while True: ... food = yield food_list ... food_list.append(food) ... >>> e=eater() >>> next(e) Ready to eat [] >>> e.send('蒸羊羔') ['蒸羊羔'] >>> e.send('蒸熊掌') ['蒸羊羔', '蒸熊掌'] >>> e.send('蒸鹿尾儿') ['蒸羊羔', '蒸熊掌', '蒸鹿尾儿'] 4. 生成器的优点
使用 yield 关键字或者生成器表达式可以很方便的生成一个迭代器对象。 为了说明这一点,我们来比较一下对于一个需求的不同实现。 该需求很简单:获取前 n 个自然数。 1、构造一个数组然后返回。当 n 很小的时候,该实现没有什么问题,但是当 n 变得很大,内存是吃不消的。 2、用生成器模式,不用 yield。构造一个对象,并实现__iter__ 和 __next__方法。但为了实现一个简单的需求却不得不构造冗长的代码。 3、使用了 yield 构造一个生成器函数 # 产生项目而不是返回列表的生成器 def firstn(n): num = 0 while num < n: yield num num += 1 sum_of_first_n = sum(firstn(1000000))
这一条主要是针对内存使用上来说的。 因为迭代器不会保存所有值,而是在运行中动态的计算出数列的各个值,并将之前的数值扔掉。 这里举个实例。 假设我们有一个很大的文件(比如说 16G ) ,但是你的电脑只有 8G 内存,你如何利用 Python 对其进行处理? 答案是使用 yield 构造生成器。 def read_by_chunks(file, chunk_size=1024): while True: data = file.read(chunk_size) if not data: break yield data f = open('your_big_file.dat') for chunk in read_by_chunks(f): process_chunk(chunk) 这种通过构造生成器逐块读取的方法叫做惰性加载,也叫流式读取,是处理大文件的一种常见方式。 惰性加载也被运用在软件设计和网页设计当中,其特征为用户通过鼠标,滚动浏览页面,直到页面下方时,就会自动加载更多内容。 如果你的是文本文件,可以按行读取,那代码就更简单了: with open('your_big_file.txt') as f: for line in f: process_line(line) Python文件句柄(file handle)就是一个迭代器,默认会将文件按行分割以惰性加载。 结语以上这些,就是Python迭代器与生成器的基础知识,希望对大家有所帮助。 如果大家有任何疑问请给我留言,我会尽快回复大家。在此也非常感谢大家对CSDN的支持! |
|
来自: wenxuefeng360 > 《待分类1》