迭代器
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有元素被访问晚结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter()和next()
可迭代对象:
我们将遵循可迭代协议的称为可迭代对象。例如:str、list、tuple、dict、set
下面我们看一段错误代码来解释可迭代对象:
对的:
s='abc'
for i in s:
print(i)
输出:
a
b
c
错的:
for i in 123:
print(i)
结果:
Traceback (most recent call last):
File "E:/pycharm file/Python学习之路/迭代器/diedaiqi.py", line 9, in <module>
for i in 123:
TypeError: 'int' object is not iterable
注意:报错信息提示“ ‘int’ object is not iterable” 意思是整数类型对象是不可迭代的。iterable表示可迭代的。
判断数据类型是否符合可迭代协议(dir函数)
那么,如何判断你的数据类型是否符合可迭代协议。我们可以通过dir函数来查看类中定义好的所有方法。
s="我的选择"
print(dir(s)) #可以打印对象中的方法和函数
print(dir(str))#也可以打印类中申明的方法和函数
在打印结果中查看__iter__若果存在,那么这个类的对象就是一个可迭代对象。
打印结果:
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
Process finished with exit code 0
以同样的方式查看list、tuple、dict、set。我们都能找到__iter__函数,我们发现这几个可以进行for循环的都有__iter__函数,包括range也有。
判断迭代器(Iterator)和迭代对象(Iterable):isinstance()函数
我们还可以通过isinstance()函数来查看一个对象是什么类型的。(Iterable 可迭代对象;Iterator 迭代器)
l=[1,2,3]
l_iter=l.__iter__()
from collections import Iterable
from collections import Iterator
print(isinstance(l,Iterable))
print(isinstance(l,Iterator))
print(isinstance(l_iter,Iterable))
print(isinstance(l_iter,Iterator))
True
False
True
True
如果对象中有__iter__函数,那么这个对象遵守可迭代协议。就可以获取相应的迭代器。__iter__是获取对象的迭代器。我们使用迭代器中的__next__()来获取一个迭代器中的元素。
s="我爱中国"
c=s.__iter__() #获取迭代器
print(c.__next__()) #使用迭代器进行迭代,获取第一个元素 #我
print(c.__next__()) #爱
print(c.__next__()) #中
print(c.__next__()) #国
print(c.__next__()) #StopIteration
由上面实例可知,当超过要迭代的元素长度时,会报错“ StopIteration ”
使用while循环+迭代器来模拟for循环(掌握)
lst=[1,2,3]
lst_iter=lst.__iter__() #1.获取迭代器
while True: #2.使用while循环获取数据
try:
data=lst_iter.__next__() #3.使用迭代器的__next__()方法来获取数据
print(data)
except StopIteration: #4.处理异常
break
总结:
Iterable:可迭代对象,内部包含__iter__()函数
Iterator:迭代器。内部包含__iter__同时包含__next__()
迭代器的特点:
- 1.节省内存
- 2.惰性机制(只有当使用迭代器的__next__函数才会获取下一个元素)
- 3.不能反复,只能向下执行
生成器
生成器实质就是迭代器
在python中有三种方式来获取生成器:
1.通过生成器函数
2.通过各种推导式来实现生成器
3.通过数据的转换获取生成器
首先,我们看一个很简单的函数:
def func():
print("abc")
return 111
ret=func()
print(ret)
结果:
abc
111
将函数中的return换成yield就是生成器
def func():
print("abc")
yield 111
ret=func() #获取生成器
print(ret)
结果:
<generator object func at 0x000001F1AD448EB8>
当我们调用func()不再是执行函数,而是获取生成器,我们可以直接执行__next__()来执行以下生成器:
def func():
print("娃哈哈")
yield 1 #return 和yield 都可以返回数据
print("呵呵呵")
gen=func() #不会执行函数 拿到的是生成器
ret=gen.__next__() #会执行到下一个yield 第一次执行第一个yield 1
print(ret)
gen.__next__() #执行到下一个yield 没有下一个 报错StopIteration
结果:
Traceback (most recent call last):
娃哈哈
File "E:/pycharm file/Python学习之路/生成器/shengchengqi.py", line 24, in <module>
1
呵呵呵
gen.__next__()
StopIteration
当程序执行完最后一个yield,在后面再继续进行__next__()程序就会报错。
举一个例子:我在商店购买10000套衣服
一般写法比较直接,一次性拿到10000套衣服,但是这样占用内存较大,由于数据太大,就没写结果
def order():
lst=[]
for i in range(10000):
lst.append("衣服" + str(i))
print(lst)
return lst
ll=order()
还有一种就是改用生成器,不占用内存,将衣服一套一套的拿。生成器就是一个一个的指向下一个,不会回去
def order():
for i in range(10000):
yield "衣服"+str(i)
g=order() #获取生成器
n1=g.__next__() #拿第一套衣服
print(n1)
n2=g.__next__() #拿第二套衣服
print(n2)
结果:
衣服0
衣服1
send方法
send和__next__()一样都可以让生成器执行到下一个yield。不同的是send可以给上一个yield位置传值
接下来看一个例子:
def eat():
print("早餐吃什么")
a=yield 1
print("a=",a) #a=包子
b=yield 2
print("b=",b) #b=馒头
c=yield 3
print("c=",c) #c=油条
yield "OVER" #最后必须以yield收尾 最后一个yield不能传值
gen=eat() #获取生成器
ret1=gen.__next__() #因为没有上一个yield,不能传值,只能用__next__
print(ret1)
ret2=gen.send("包子")
print(ret2)
ret3=gen.send("馒头")
print(ret3)
ret4=gen.send("油条")
print(ret4)
结果:
早餐吃什么
1
a= 包子
2
b= 馒头
3
c= 油条
OVER
执行过程:
注意:send是给上一个yield传值
send和__next__()区别:
- send 和next()都是让生成器向下执行一次
- send可以给上一个yield的位置传值,不能给最后一个yield发送值,在第一次执行生成器代码时不能使用send()
生成器可以用for循环来获取内部的元素:
#生成器可以用for循环来获取内部元素
def fun():
yield 666
yield 888
yield 999
for i in fun(): #fun()是生成器 for循环的内部一定有__next__()
print(i)
"""
结果:
666
888
999
"""
print(list(fun())) #list内部有__next__()
#[666, 888, 999]
生成器表达式
生成器表达式和列表推导式的语法基本上是一样的,只需要把 [] 替换成()
gen=(i*i for i in range(10))
print(gen)
结果:
<generator object <genexpr> at 0x0000026452B90518>
打印的结果是一个生成器,接下来使用for循环来循环这个生成器:
gen=(i*i for i in range(10))
#用for循环来循环生成器
for a in gen:
print(a)
打印结果:
0
1
4
9
16
25
36
49
64
81
生成器表达式也可以进行筛选:
例1:
gen=(i*i for i in range(10) if i%3==0) #筛选出10以内能被3整除的数的平方
print(gen)
#用for循环来循环生成器
for a in gen:
print(a)
结果:
0
9
36
81
例2:
#寻找名字中带有两个aa字母的名字
names=['qwe','ert','asd','saa','faa']
gen=(name for name in names if name.count("a")>=2)
for name in gen:
print(name)
结果:
saa
faa
生成器表达式和列表推导式的区别:
- 1.占用内存不一样。列表推导式比较耗内存。一次性加载;生成器表达式几乎不占用内存,使用的时候才分配和使用内存
- 2.得到的值不一样。列表推导式得到的是一个列表;生成器表达式获取的是一个生成器
生成器的惰性机制:生成器只有在访问的时候才取值
各种推导式
字典推导式
推导出来的是字典
#字典推导式
dic={'a':1,'b':2}
new_dic={dic[key]:key for key in dic}
print(new_dic)
结果:
{1: 'a', 2: 'b'}
#从lst1中获取的数据和lst2中相应的位置的数据组成一个新字典
lst1=['aa','bb','cc']
lst2=['11','22','33']
dic={lst1[i]:lst2[i] for i in range(len(lst1))}
print(dic)
结果:
{'aa': '11', 'bb': '22', 'cc': '33'}
面试题:由[11,22,33,44]生成字典{0:11,1:22,2:33,3:44}
lst=[11,22,33,44]
dic={i:lst[i] for i in range(len(lst))}
print(dic)
结果:
{0: 11, 1: 22, 2: 33, 3: 44}
集合推导式
集合推导式可以直接生成一个集合。集合的特点:无序、不重复,所以集合推导式自带去重功能
lst=[1,-1,2,-2,3]
#绝对值去重
s={abs(i) for i in lst}
print(s)
结果:
{1, 2, 3}
推导式有:列表推导式、字典推导式、集合推导式,没有元组推导式
一个面试题 :难度系数较大 惰性机制 不到最后不会拿值
#求和
def add(a,b):
return a + b
#生成器函数
def test():
for r_i in range(4): #0-3
yield r_i
g=test()#获取生成器
#两个g都是生成器
for n in [2,10]:
g=(add(n,i) for i in g)
print(list(g))
结果:
[20, 21, 22, 23]
分析:
for n in [2,10]: g=(add(n,i) for i in g)
拆分后:
for n =2: g=(add(n,i) for i in g)
for n =10: g=(add(n,i) for i in g) #此时的g得到的是n=2时的g值
因为生成器的惰性机制,不到最后不拿值,n=2时,没有取值,改写为:
for n =10: g=(add(n,i) for i in (add(n,i) for i in g)) #(add(n,i) for i in g)->(add(n,i) for i in 0,1,2,3)->add(10,0123)->10,11,12,13
->g=(add(n,i) for i in 10,11,12,13) #i=10,11,12,13
->g=(add(n,i)) =>20,21,22,23