1 迭代Iteration
迭代Iteration:所谓迭代就是重复运行一段代码语句块的能力,就好比在一个容器中进行一层一层遍历数据,在应用过程中for循环最为突出。迭代就是从某个容器对象中逐个地读取元素,直到容器中没有元素为止。迭代迭代,更新换代,在上一次基础上更新成新的东西。
# 使用for循环迭代这个字符串,其实就是我们说的遍历这个字符串
for i in "hello world":
print(i)
2 可迭代对象Iterable
2.1 什么是可迭代对象
可迭代对象Iterable
:可以被迭代的类型,怎么判断是否可迭代?
所有的类型只要有__iter__()
方法就是可迭代的。我们现在已知的可迭代对象有:str,list,tuple,range,set,dict_keys,dict_values,dict_items
。
方法名前后有两个下划线的,也叫魔法方法。
2.2 怎么判断可迭代对象
怎么去判断某个对象是否有__iter__()
方法?
-
用
dir()
函数来查询是否包含__iter__()
方法; -
通过
对象.方法
的方式去调用看有没有__iter__
方法; -
通过内置的实例对象函数
isinstance()
判断,可迭代对象是Iterable
类的实例,返回True就说明是可迭代对象,False则表示不是可迭代对象。isinstance()函数格式:isinstance(object, class)
。
# 用dir()函数打印数据的全部方法,看看是否包含__iter__()方法
print(dir(list1))
# 查看某个元素或序列是否有.__iter__()方法,有就是可迭代的对象
"hello world".__iter__()
[2].__iter__()
(1,).__iter__()
{"name":"jack"}.__iter__()
{"name":"jack"}.keys().__iter__()
range(11).__iter__()
list1=[]
a=9
# 通过内置的实例对象函数isinstance()判断
from collections.abc import Iterable,Iterator
print(isinstance(list1,Iterable)) # 结果是True
print(isinstance(a,Iterable)) # 结果是False
3 迭代器Iterator
3.1 什么是迭代器
-
迭代器
Iterator
:迭代器一定是可迭代对象,迭代器中有两个特殊的方法是__iter__()
和__next__()
方法 -
创建迭代器的方法:使用内置的
iter()
函数或者__iter__()
方法来创建迭代器 -
举例:
# 将列表转换为迭代器 list1=[1,3,5,7,9] it1=iter(list1) it2=list1.__iter__() print(type(it1)) print(type(it2)) # 打印迭代器中的数据,每次只能打印一个元素,有多少元素就需要打印多少次 # 用next()函数取迭代器中的数据 print(next(it1)) # 用__next__()方法取迭代器中的数据 print(it1.__next__()) print(it1.__next__()) print(it1.__next__()) print(it1.__next__()) # print(it1.__next__()) # print(it1.__next__())
-
可迭代对象与迭代器关系示意图:
举例:a = iter([4, 3, 6, 8])
-
迭代器的特点是:
-
迭代器一定是可迭代对象;
-
迭代器通过next()函数或者
__next__()
方法取值,每迭代一次取一个值,只能往前取不能后退; -
当取完最后一个值的时候,再执行next()函数或者
__next__()
方法会报StopIteration
异常,表示取完了。
-
3.2 怎么判断迭代器
判断一个对象是迭代器还是迭代对象,可以使用以下两种方法:
-
通过对象包含
__iter__()
和__next__()
方法来判断; -
需要判断是迭代器还是迭代对象,可以通过实例对象函数isinstance()进行判断,迭代器和可迭代对象的对象类型分别是
Iterator
和Iterable
(都是collections.abc
模块下)
from collections.abc import Iterator,Iterable
# Iterator是判断是否是迭代器
# Iterable是判断是否是可迭代对象
print(isinstance(list1,Iterable)) #判断list1是否是可迭代对象返回True或False
print(isinstance(list1,Iterator)) #判断list1是否是迭代器返回True或False
print(isinstance(iter(list1),Iterator)) #True
print(isinstance(it1,Iterable)) #True
print(isinstance(it1,Iterator)) #True
#计算1+2+3+...+1000000000
#print(sum(list(range(1,1000000001))))
#print(sum(iter(range(1,1000000001))))
迭代器的优缺点:
-
迭代器优点:节省内存,迭代器在内存中相当于只占一个数据的空间,因为每次取值都会把上一条数据在内存中释放,加载当前的此条数据。
-
迭代器的缺点,不能直观的查看里面的数据。取值时不走回头路,只能一直向下取值。
3.3 for循环的底层原理
for循环实现遍历的底层原理:(for循环将其都封装好了,所以for循环in后面必须跟的是可迭代对象)
-
for循环可以传入可迭代对象或者迭代器对象,在循环的时候先调用可迭代对象的
iter()
函数来生成迭代器; -
调用迭代器的
next()
函数来迭代每个元素; -
当迭代完最后一个元素时,再继续迭代会报
StopIteration
异常,for循环捕捉到这个异常就知道已经迭代完了,就结束循环。
for循环的实现原理:
也可以用for循环去遍历迭代器。
4 生成器Generator
4.1 什么是生成器
生成器Generator:在 Python 中,生成器的本质就是一个迭代器,使用了yield 的函数被称为生成器(generator)。
4.2 生成器怎么创建
创建生成器的方法:
-
方法一:
-
第一步:定义一个包含yield语句的函数
-
第二步:调用第一步创建的函数得到一个生成器
# 例1:使用yield关键字把函数变成装饰器
def generator_1():
yield 1
# 创建生成器,如下表示创建一个生成器,赋值给gen
gen=generator_1()
print(type(gen))
# 在函数中可以使用多个yield
def generator_2():
yield 1
yield 2
yield 3
# 也可以一个yield语句返回多个值,跟使用return类似,此时将返回一个元组
def generator_3():
yield 1, 2
-
方法二:使用生成器推导式来创建生成器
# 例2:通过生成器推导式创建生成器
print(type((i for i in range(11))))
print((i for i in range(11)))#是一个生成器对象
4.3 return与yield的区别
return的作用:
-
给调用者返回值;
-
执行遇到第一个return语句时就结束函数。
yield的作用:
-
给调用者返回值;
-
yield把函数变成了生成器;
-
生成器运行时遇到yield后先返回再挂起;
-
在函数中可以使用多个yield。
4.4 生成器的运行
带有 yield 的函数执行过程比较特别:
-
调用该函数的时候不会立即执行代码,而是返回了一个生成器对象;
-
当使用
next()
函数或者__next__()
方法作用于返回的生成器对象时,函数开始执行,在遇到 yield 的时候会『暂停』,并返回当前的迭代值; 也可以使用for循环来迭代生成器对象,因为在for循环中会自动调用next()
函数; -
当再次使用
next()
的时候,函数会从原来『暂停』的地方继续执行,直到遇到 yield语句,如果没有 yield 语句,则抛出异常;
整个过程看起来就是不断地 执行->中断->执行->中断
的过程。一开始,调用生成器函数的时候,函数不会立即执行,而是返回一个生成器对象;然后,当我们使用 next() 作用于它的时候,它开始执行,遇到 yield 语句的时候,执行被中断,并返回当前的迭代值,要注意的是,此刻会记住中断的位置和所有的变量值,也就是执行时的上下文环境被保留起来;当再次使用 next() 的时候,从原来中断的地方继续执行,直至遇到 yield,如果没有 yield,则抛出异常。简而言之,就是 next() 使函数执行, yield 使函数暂停。
# 在定义函数时,使用yield来返回值
def generator_1():
yield 1
yield 2
yield 3
# 直接这个函数的调用结果,得不到返回的值
print(generator_1())
# 使用next()函数或者__next__()方法来运行该函数,但如下每次执行时都返回第一个值,因为每次调用的时候都会创建一个生成器
print(next(generator_1()))
print(next(generator_1()))
print(generator_1().__next__())
# 可以调用一次函数,对返回结果赋值给一个变量,这样可以获取生成器所有返回的值
def generator_1():
yield 1
yield 2
yield 3
# 调用函数,返回一个生成器对象,赋值给变量gt
gt=generator_1()
# 通过next()函数来执行这个生成器对象
print(next(gt))
print(next(gt))
# 使用for循环执行生成器
for i in gt:
print(i)
4.5 send()方法
通过send()方法也可以执行生成器,同时可以向生成器传入值。
send()方法与next()函数的区别:
-
send()
方法与next()
函数都用来执行生成器; -
send()
方法会将传入的值赋给上次中断时yield语句的执行结果,然后再执行生成器,从而实现与生成器方法的交互; -
在执行生成器时,如果第一次执行使用
send()
方法,因为没有挂起的yield语句来接收传入的值,所以会报TypeError
异常。 -
简单地说,
send()
方法就是next()
函数的功能,加上传值给 yield 。
# 例1:第一次使用send()方法执行生成器
def generator_1():
yield 1
yield 2
yield 3
gt1=generator_1()
# 如下代码会报错,因为第一次需要先使用next()函数来执行生成器
print(gt1.send(100))
print(gt1.send(200))
# 例2:第一次使用next()执行生成器,第二次开始使用send()执行生成器并传入值
def generator_2():
a=yield 1
b=yield a
yield b
c=yield 2
print(c)
yield c
gt2=generator_2()
# 首先使用next()函数执行生成器,返回1之后挂起
print(next(gt2))
# 使用send()方法传入100给yield 1的执行结果,也就是a,然后再执行yield a,返回100,再挂起
print(gt2.send(100))
# 使用send()方法传入2.5给yield a的执行结果,也就是b,然后再执行yield b,返回2.5,再挂起
print(gt2.send(2.5))
# 使用send()方法传入'abc'给yield b的执行结果,但并没有引用他,然后执行yield 2,返回2,再挂起
print(gt2.send('abc'))
# 传入efg给yield 2的执行结果,也就是c,然后再打印c,最后再执行yield c,返回efg
print(gt2.send('efg'))
4.6 生成器与迭代器的区别
-
生成器:
-
生成器本身是一种特殊的迭代器,也就是说生成器就是迭代器。
-
生成器会自动实现迭代器协议,也就是说只要我们yield后,自动就生成了next对象包括StopIteration等结构。
-
生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息。对生成器函数的第二次(或第n次)调用,跳转到函数上一次挂起的位置。生成器不仅“记住”了它的数据状态,生成还记住了程序执行的位置。
-
-
迭代器:
-
迭代器是一种支持next()操作的对象。它包含了一组元素,当执行
next()
操作时,返回其中一个元素; -
当所有元素都被返回后,再执行
next()
报异常StopIteration
; -
生成器一定是可迭代的,也一定是迭代器对象。
-
-
它们的区别:
-
迭代器是访问容器的一种方式,也就是说容器已经出现。我们是从已有元素拓印出一份副本,只为我们此次迭代使用。而生成器则是自己生成元素的。也就是前者是从有到有的复制,而后者则是从无到有的生成。
-
在用法上生成器只需要简单函数写法,配合yield就能实现。而迭代器真正开发中很难使用到。我们可以把生成器看做,python给我们提供的特殊接口实现的迭代器。
最后,附上一张图来解释容器、可迭代对象、迭代器、生成器之间的关系:
4.7 生成器使用举例:处理大量数据
生成器的优势在于在没有牺牲很多的速度情况下,内存占用更小,在一定的业务场景下,支持大数据的操作。
举例:通过列表和生成器分别处理1亿条数据对比
# 通过列表实现,如下代码在执行时可观察电脑的内存使用情况,内存会被占满
a = []
for i in range(100000000):
temp = ['你好']*2000
a.append(temp)
[[你好,你好,...,你好],[你好,你好,...,你好],...,[你好,你好,...,你好]]
for ele in a:
# print(ele)
continue
# 通过生成器实现,如下代码在执行时可观察电脑的内存使用情况,内存占用不大
def get_list_element():
for i in range(100000000):
temp = ['你好']*2000
yield temp
# 创建一个生成器
a = get_list_element()
for ele in a:
# print(ele)
continue