一、迭代器 Iterator 和 生成器 Generator
1.什么是迭代器:
迭代器是访问可迭代对象的一种方式,用迭代器可以访问可迭代
迭代器是指iter(可迭代对象)返回的对象
迭代器可以用next(it)函数获取可迭代对象中的数据
2.迭代器相关的函数
1)iter(iterable)
从可迭代对象中返回一个迭代器,iterable必须是能提供一个迭代器的对象
2)next(iterator)
从迭代器iterator中获取下一个元素。如果无法获取下一个元素,则触发StopIterator异常
3.迭代器说明:
迭代器只能往前取值,不会后退
用iter函数可以返回一个可迭代对象的迭代器
示例1:以前我们访问可迭代对象使用for循环,这里示例迭代器访问
for循环打印可迭代对象原理,实际上while循环 + iter 迭代器方式等价
见示例3
for x in L: #实际也是用可迭代对象的迭代器访问的
print(x)
L = [1,2,3,4,5,6]
it = iter(L)
print(it)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
执行结果:
<list_iterator object at 0x0000018838463828>
1
2
3
4
5
6
StopIteration
#当可迭代对象中的元素被遍历完后,继续遍历会触发一个StopIteration的异常
示例2:用迭代器访问获取range对象的数据
it = iter(range(5,10))
print(it)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
执行结果:
5
6
7
8
9
StopIteration
示例3.用while循环访问可迭代对象中的元素,并且进行异常捕获
for循环遍历可迭代对象其实就是对while循环 + iter +异常处理进行的封装
L = [1,2,3,4,5,6]
it = iter(L)
while True:
try:
x = next(it)
except StopIteration:
break
print(x)
执行结果:
1
2
3
4
5
6
示例4:分别用for循环和while循环访问可迭代对象s
s = {"工商银行","建设银行","中国银行","农业银行"}
for x in s:
print(x,end = ' ')
print()
it = iter(s)
while True:
try:
x = next(it)
print(x, end=' ')
except StopIteration:
break
执行结果:
工商银行 农业银行 建设银行 中国银行
工商银行 农业银行 建设银行 中国银行
二、生成器 genertor (python 2.5及以后的版本)
1.什么是生成器
生成器是能够动态提供数据的对象,生成器对象也是可迭代对象
2.生成器的种类:
生成器函数
生成器表达式
3.生成器函数定义:
含有yield语句的函数是 生成器函数,此函数被调用将返回一个生成器对象
yield翻译为产生或生成
4.yield语句:
语法:
yield 表达式
说明:
yield 用于def 函数中,目的是将此函数作为生成器函数使用
yield用来生成数据,共next(it)函数使用
动态提供数据和静态提供数据:
静态:已经存在,直接拿来用
L = [1,2,3,4]
for x in L:
print(x)
#实际上x取的数据已经存在在内存中,我们直接拿来用就OK,不是运行的时一个一个的生成
动态:运行的时候再去生成这些数据
for x in range(1000000000000000000):
print(x)
#实际上,range不会一次性就将这所有的数据都生成出来,而是会运行时再去一个一个的生成,访问过的被释放,“现用现生成”,会使计算机效率更高
示例1:定义一个yield函数
def myyield():
yield 2
yield 2+1
yield 5
yield 7
print("生成器函数调用结束")
myyield()
执行结果:无
#并没有打印函数中的要打印的语句,这是为什么?
示例2:解释示例1的现象
myyied函数实际上返回来的时生成器对象,即使没有return语句,该对象为可迭代对象
#即使调用了这个函数,实际上并没有执行,因为没有打印
gen = myyield()
print(gen)
执行结果:
<generator object myyield at 0x00000219F1727DB0>
既然这个生成器对象可以迭代,我们可以看一下有什么东西
for x in gen:
print(x,end = " ")
执行结果:
2 3 5 7 生成器函数调用结束
那当然可以用它返回的迭代器访问它了,这里我们使用while循环来访问这个生成器对象
it = iter(gen)
while True:
try:
x = next(it)
print(x,end = " ")
except StopIteration:
break
执行结果:
2 3 5 7 生成器函数调用结束
图解这个特殊函数的调用中的各种复杂关系:
上图补充一句:等到生成器对象去向函数要数据的时候,这个生成器函数才会被调用
也就是说:gen = myyield()并没有调用函数
为什么要有生成器:
一切为了效率!!!!!! 现用现生成!!!!!!
实际上有的我们可以不必为用户提前生成一大堆的数据:
优点:
1.生成一大堆数据会占用大量的内存,就拿我们熟悉的列表来说,我们定义好了,系统就要为我们创建那么多的对象,相应的也会占用大量的内存,在数据量非常大的时候,静态的提供数据效率就显得很低了
2.能快速的响应客户,不用客户等待那么久,生成一个给用户送一个,我觉得用户可能更喜欢这样的响应方式,而不是等待很久所有数据生成后才得到回应。
缺点:
需要进行生成器函数、生成器对象、迭代器之间来回的传递数据,可能会浪费一些时间
总结:
对于海量数据问题时,函数生成器可以降低内存占用率,降低用户响应的等待时间
练习:
写一个生成器函数,my_integer(n)生成1到n之间的整数
def my_integer(n):
i = 1
while i< n:
yield i
i += 1
for x in my_integer(10):
print(x,end =' ')
执行结果:
1 2 3 4 5 6 7 8 9
range():就可以用yield来实现
练习:给定一个区间[start,end),使用函数生成器找出区间中的所有奇数,并打印出来
def myodd(start,end):
i = start
while i < end:
if i % 2 != 0:
yield i
i += 1
i += 1
it = iter(myodd(1,60))
while True:
try:
x = next(it)
print(x,end = ' ')
except StopIteration:
break
执行结果:
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59
三、生成器表达式
语法:
(表达式 for 变量 in 可迭代对象 [if 真值表达式])
注: []内的if部分可以省略
作用:
用推导的形式去生成一个新的生成器
示例:
gen = (x ** 2 for x in range(1,5))
it = iter(gen)
print(gen)
while True:
try:
x = next(it)
print(x,end = ' ')
except StopIteration:
break
执行结果:
<generator object <genexpr> at 0x000001FA66237DB0>
1 4 9 16
注:python中实际上有三种推导式:列表、集合、字典。
这里只是以推导式的形式来生成一个生成器
我们也可以从执行结果看出来,gen是个生成器对象
图解生成器表达式:
注:python中的生成器函数和表达式是处理海量数据的优势。不需要将数据完全存下来,取一个处理一个,效率比较高
四、迭代工具函数:
迭代工具函数的作用是生成一个个性化的可迭代对象
zip(iter1[,iter2,…]):
返回一zip对象,此对象用于生成一个元组,此元组的个数是由最小的迭代对象决定的
元组的内容始可迭代对象iter1和iter2中的元素的组合
enumerate(iterable[,start]):
生成带索引的枚举对象,返回的迭代器类型为索引 - 值 对儿(index - value)对
默认索引从0开始,也可以用start指定
注:[]内的内容可以省略
示例:zip用法
numbers = [10086,10000,10010,95588] #四个元素
names = ['中国移动','中国电信','中国联通']#三个元素
for t in zip(numbers,names):
print(t)
#每次分别从俩个可迭代对象中去一个元素,打包成一个元组
#如果其中的一个短的结束,打包的过程也结束,短板效应,以最短的为标准
#还可以将生成这些元组打包放在一个字典
d = dict(zip(numbers , names))
print(d)
执行结果:
(10086, '中国移动')
(10000, '中国电信')
(10010, '中国联通')
{10000: '中国电信', 10010: '中国联通', 10086: '中国移动'}
练习:自己动手写一个zip函数
def myzip(iter1,iter2):
it1 = iter(iter1)
it2 = iter(iter2)
while True:
try:
yield (next(it1),next(it2))
except StopIteration:
break
numbers = [10086,10000,10010,95588] #四个元素
names = ['中国移动','中国电信','中国联通']#三个元素
for t in myzip(numbers,names):
print(t)
执行结果:
(10086, '中国移动')
(10000, '中国电信')
(10010, '中国联通')
示例:enumerate的用法,可以给一些数据加上序列,相当于给已经有的可迭代对象加上数据,进行包装。
names = ['中国移动','中国电信','中国联通']
for n in enumerate(names):
print(n)
#自动会给数据加上0,1,2...索引
执行结果:
(0, '中国移动')
(1, '中国电信')
(2, '中国联通')
示例:第二个参数的作用
names = ['中国移动','中国电信','中国联通']
for n in enumerate(names,100):
print(n)
执行结果:
(100, '中国移动')
(101, '中国电信')
(102, '中国联通')
示例: enumerate的扩展写法
names = ['中国移动','中国电信','中国联通']
for k,n in enumerate(names,100):
print(n,"的序号是",k)
执行结果:
中国移动 的序号是 100
中国电信 的序号是 101
中国联通 的序号是 102
示例:
it = iter(enumerate(names,1))
while True:
try:
k,n = next(it)
print("序号:",k,'------>',n)
except StopIteration:
break
执行结果:
序号: 1 ------> 中国移动
序号: 2 ------> 中国电信
序号: 3 ------> 中国联通
练习:输入一些字符串,输入空格时结束输入,打印带有行号的字符串
L = []
def fun():
while True:
s = input("输入字符串: ")
if s == '':
break
else:
L.append(s)
for x,n in enumerate(L,1):
print('序号:',n,"---------->",x)
fun()
执行结果:
输入字符串: hello
输入字符串: python
输入字符串: 小鸡鸡
输入字符串:
序号: hello ----------> 1
序号: python ----------> 2
序号: 小鸡鸡 ----------> 3