本节主要内容:
1.迭代器
2.生成器
一、迭代器
我们之前一直再用可迭代对象进行迭代操作,那么到底什么是可迭代对象.本小节主要讨论可迭代对象。首先我们先回顾下目前我们所熟悉的可迭代对象有哪些:
str,list,tuple,dict,set.那么为什么我们可以称他们为可迭代对象呢?因为他们都遵循了可迭代协议,什么是可迭代协议,首先我们先看一段错误代码:
#对的 s ="abc" for i in s : print(i) #错的 for i in 123: print(i) 结果: Traceback (most recent call last): File "/Users/sylar/PycharmProjects/oldboy/iterator.py", line 8, in <module> for i in 123: TypeError: 'int' object is not iterable
注意看报错信息中有这样一句话.'int' object is not iterable.翻译过来就是整数类型对象是不可迭代的。iterable表示可迭代的,我们可迭代协议,那么如何进行验证你的数据类型是否符合可迭代协议.我们可以通过dir函数来查看类中定义好的所有方法。
s = "我的哈哈哈" print(dir(s)) #可以打印对象中的方法和函数 print(dir(str)) #也可以打印类中声明的方法和函数
在打印结果中.寻找__iter__如果能找到,那么这个类的对象就是一个可迭代对象.
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__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']
我们发现字符串中也可以找到__iter__.继续看一下list,tuple,dict,set
print(dir(tuple)) print(dir(list)) print(dir(open("护士少妇嫩模.txt"))) #文件对象 print(dir(set)) print(dir(dict)) 结果: ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] ['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'writelines'] ['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update'] ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
我们发现这几个可以进行for循环的东西都有__iter__函数,包括range也有,可以自己试一下.
综上.我们可以确定,如果对象中有__iter__函数,那么我们认为这个对象遵守了可迭代协议.就可以进行迭代.这里的__iter__是帮助我们获取到对象的迭代器,我们使用__next__()来获取到一个迭代器中的元素,我们之前讲的for的工作原理到底是什么?继续看代码
s = "我爱北京天安门" c = s.__iter__() #获取迭代器 print(c.__next__()) #使用迭代器进行迭代,获取一个元素 我 print(c.__next__()) # 爱 print(c.__next__()) # 北 print(c.__next__()) # 京 print(c.__next__()) # 天 print(c.__next__()) # 安 print(c.__next__()) # 门 print(c.__next__()) # StopIteration
我们可以把要迭代的内容当成子弹,然后呢,获取迭代器__iter__(),就是子弹都装在弹夹中,然后发射就是__next__()把每一个子弹(元素)打出来.也就是说,for循环的时候,一开始的时候是__iter__()来获取迭代器,后面每次获取元素都是通过__next__()来完成,当程序遇到Stoplteration将结束循环.
二.生成器
什么是生成器,生成器是指就是迭代器
在python中有三种方式来获取生成器:
1.通过生成器函数
2.通过各种推导式来实现生成器
3.通过数据的转换也可以获取生成器
今天,我们主要使用生成器函数来完成生成器的创建和使用
首先,我满先看一个很简单的函数:
def func(): print("111") return 222 ret = func() print(ret) 结果: 111 222
将函数中的return换成yield就是生成器
def func(): print("111") yield 222 ret = func() print(ret) 结果: <generator object func at 0x0067AF90>
运行的结果和上面不一样,为什么呢,由于函数中存在了yield,那么这个函数就是一个生成器函数,这个时候,我们再执行这个函数的时候,就不再是函数的执行了,而是获取这个生成器.如何使用呢?想一想迭代器,生成器的本质就是迭代器,所以.我们可以直接执行__next__()来执行以下生成器。
def func(): print("111") yield 222 gener = func() # 这个时候函数不会执行,而是获取到生成器 ret = gener.__next__() # 这个时候函数才会执行,yield的作用和return一样,也是返回数据 print(ret) 结果: 111 222
那么我们可以看到,yield和return的效果是一样的,有什么区别呢?yield是分段来执行一个函数,return呢?直接停止执行函数。
def func(): print("111") yield 222 print("333") yield 444 gener = func() ret = gener.__next__() print(ret) ret2 = gener.__next__() print(ret2) ret3 = gener.__next__() # 最后一个yield执行完毕,再次__next__()程序报错,也就是说,return无关了 print(ret3) 结果: Traceback (most recent call last): 222 333 File "/Users/sylar/PycharmProjects/oldboy/iterator.py", line 55, in <module> 444 ret3 = gener.__next__() # 最后⼀个yield执⾏完毕. 再次__next__()程序报错, 也 就是说. 和return⽆关了. StopIteration
当程序运行完最后一个yield,那么后面继续进行__next__()程序会报错.
好了生成器说完了,生成器有什么作用呢?我满来看这样一个需求,老男孩想JACK JONES订购10000套学生服,JACKJONES就比较实在,直接造出10000套衣服。
def cloth(): lst = [] for i in range(0, 10000): lst.append("衣服" + str(i)) return lst c1 = cloth() print(c1)
但是呢,问题来了,老男孩现在没有这么多学生啊,一次性给问我这么多,我往哪里放啊,很尴尬啊,最好的效果是什么样呢?我要1套,你给我一套,一套工10000套,是不是很完美的
def cloth(): for i in range(0, 10000): yield "衣服" + str(i) c1 = cloth() print(c1.__next__()) print(c1.__next__()) print(c1.__next__()) print(c1.__next__())
区别:第一种是直接一次性全部拿出来,会很占内存,第二种使用生成器,一次就一个,用多少生多少,生成器是一个一个的指向下一个,不会回去,__next()到哪,指针就指到那儿,下一次继续获取指针指向的值。
接下来我们来看send方法。send和__next__()一样都可以让生成器执行到下一个yield。
def eat(): print("我吃什么啊") a = yield "馒头" print(a) b = yield "大饼" print(b) c = yield "韭菜盒子" print(c) print("程序结束") yield "GAME OVER" gen = eat() # 获取生成器 ret1 = gen.__next__() print(ret1) ret2 = gen.send("胡辣汤") print("ret2") ret3 = gen.send("狗粮") print(ret3) ret4 = gen.send("猫粮") print(ret4)
send 和__next__()区别:
1.send和next()都是让生成器向下走一次
2.send可以给上一个yield的位置传递值,不能给最后一个yield发送值,在第一次执行生成器代码的时候不能使用send()