迭代器
再说迭代器之前先来了解下for循环。
首先,我们对一个列表进行for循环。
for i in [1,2,3,4]:
print(i)
上面这段代码肯定是没有问题的,但是我们换一种情况,来循环一个数字1234试试
for i in 1234
print(i)
结果:
Traceback (most recent call last):
File "test.py", line 4, in <module>
for i in 1234:
TypeError: 'int' object is not iterable
结果是报错,TypeError: 'int' object is not iterable”,说int类型不是一个iterable。
那什么是可迭代的呢 ?
首先,之前的例子说明int,是不可迭代的。那么如果“可迭代”,就应该可以被for循环了。
这个我们知道呀,字符串、列表、元组、字典、集合以及range()都可以被for循环,说明他们都是可迭代的。
即如果可以将某个数据集内的数据“一个挨着一个的取出来”,就叫做迭代,那么是如何判断一个数据类型那个是可迭代的,那个又是不可迭代的呢?
这里就需要引用一个函数dir:可以用来查询一个数据类型所有的方法。
print(dir([1,2])) print(dir((1,2))) print(dir({1:2})) print(dir({1,2}))
结果显示
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__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']
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__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']
['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__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']
不难发现,它们中都含有__iter__,总结一下我们现在所知道的:可以被for循环的都是可迭代的,要想可迭代,内部必须有一个__iter__方法。
也就得到了一个名词,可迭代协议:主要含有__iter__方法的数据类型,都是可迭代的。
那么__iter__方法可以做什么呢?
print([1,2].__iter__()) 结果 <list_iterator object at 0x1024784a8> 列表[1,2]通过__iter__()的方法得到了一个迭代器iterator。
那什么是迭代器比列表又多了什么方法呢?
dir([1,2].__iter__())是列表迭代器中实现的所有方法,dir([1,2])是列表中实现的所有方法,都是以列表的形式返回给我们的,为了看的更清楚,我们分别把他们转换成集合,
然后取差集。
'''
#print(dir([1,2].__iter__()))
#print(dir([1,2]))
print(set(dir([1,2].__iter__()))-set(dir([1,2])))
结果:
{'__length_hint__', '__next__', '__setstate__'} 可以看到,相比于列表,迭代器多了三个方法分别是
iter_l = [1,2,3,4,5,6].__iter__()
#获取迭代器中元素的长度
print(iter_l.__length_hint__())
#根据索引值指定从哪里开始迭代
print('*',iter_l.__setstate__(4))
#一个一个的取值
print('**',iter_l.__next__())
print('***',iter_l.__next__())
这其中如果要使for实现遍历循环的话,就必须要用到__next__()方法了
迭代器遵循迭代器协议:必须拥有__iter__方法和__next__方法。
迭代器一定可迭代,可迭代的只需通过调用__iter_()方法,就可以得到迭代器。
迭代器的特点:使用很方便/节省内存空间、且一个迭代器所有的数据只能取一次,取完就没有了。
# num = [1,3,5,6,7,8] # a=(filter(lambda x:x%2==0,num)) #生成一个迭代器 # for i in a: # print(i) 这里可以得到 6,8 #print(list(a)) #这里等到的是[] 因为迭代器内的值都被上面for循环取完了
查看可迭代和迭代器的方法
from collections import Iterable,Iterator s1='123' list1=[1,2,3] tuple1=(1,2,3) dic1={'a':1} set1={1,2,3} f=open('a.txt') #都是可迭代,只要对象有__iter__()方法,都是可迭代的 s1.__iter__() list1.__iter__() tuple1.__iter__() dic1.__iter__() set1.__iter__() f.__iter__() #查看是否可以迭代的 print(isinstance(s,Iterable)) #查看是否是迭代器,只有文件是迭代器 f.__next__() print(isinstance(f,Iterator))
生成器
生成器是一个包含yield关键字的函数,当它被调用时,在函数中的代码不会执行,而会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句,
yield语句意味着会生成一个值,return语句一位着生成器要停止执行(不在生成任何东西,return语句只有在一个生成器中使用时才能进行无参数调用)换句话说,生成器是由两部分组成:
生成器的函数和生成器的迭代器,生成器的函数使用def定义,包括yield部分,生成器的迭代器是这个函数返回的部分.
生成器的本质就是迭代器
生成器的表现形式 1.生成器函数
2.生成器表达式
生成器函数:含有yield的函数就是生成器函数
生成器函数的特点:1.调用函数之后,函数不执行,返回一个生成器。
2.每次调用__next__()函数的时候可以取一行值,遇到yield停止。 #yield每调用一次取一个值,return则是一次性取完值
3.知道取完最后一个,再执行会报错。
# def generator(): # for i in range(20): # yield "新年好%d"%i # g=generator() #调用生成器函数得到一个生成器 # k=g.__next__() #每一次执行g.__next__就是从生成器中取值,预示着生成器函数中的代码继续执行 # print(k)
# k1=g.__next__()
# print(k1) # for i in g: #for循环可以一次性都显示出来 # print(i)
从生成器中取值的几个方法
next
for
数据类型的强制转换 : 占用内存
send:
def generator(): #定义生成器 print(123) content = yield 1 print('=======',content) print(456) yield2 g = generator()
g1= generator() #这里g1和g不相关,因为每次函数调用生成器函数,都会生成一个新的生成器 ret = g.__next__() print('***',ret) ret = g.send('hello') #send的效果和next一样 send将‘hello’传递给yield1 即content print('***',ret) #send 获取下一个值的效果和next基本一致 #只是在获取下一个值的时候,给上一yield的位置传递一个数据 #使用send的注意事项 # 第一次使用生成器的时候 是用next获取下一个值,send # 最后一个yield不能接受外部的值
send相比于next因其传递数据的特点,有了很多用法,例如求平均数。
def averager():
total = 0
count = 0
average = None
while True:
num = yield average
total += num
count += 1
average = total/count
g = averager()
g.__next__()
print(g.send(10)) #发送数据给num
print(g.send(30))
print(g.send(5))
另外在python3以上版本,可以使用yield from:
#python 3 # def generator(): # a = 'abcde' # b = '12345' # for i in a: # yield i # for i in b: # yield i # def generator(): # a = 'abcde' # b = '12345' # yield from a # yield from的作用和for循环情况下调用yield是一样的 # yield from b # # g = generator() # for i in g: # print(i)
a
b
c
d
e
1
2
3
4
5
列表推导式和生成器表达式
# #30以内所有能被3整除的数 # ret = [i for i in range(30) if i%3 == 0] #完整的列表推导式
# print(ret) # g = (i for i in range(30) if i%3 == 0) #完整的生成器推导式 #这里for之前的i,可以进行相关操作,比我i**2等
# for i in g
# print(i)
列表推导式得到的是[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]它是将列表一次性以列表的形式表达出来,而生成器表达式的结果则是将其中的元素一个一个的取出来,这样就生成器表达式节省了内存。
其他数据类型的推导式:
字典推导式:
# 例1:将一个字典的key和value对调 # func = {'a': 10, 'b': 34} # b = {func[k]: k for k in func} #通过字典的键进行for循环 # print(b)
# {10:'a' , 34:'b'}
# 例二:合并大小写对应的value值,将k统一成小写 # dic = {'a': 10, 'b': 34, 'A': 7, 'Z': 3} # #{'a':10+7,'b':34,'z':3} # b = {k.lower(): dic.get(k.lower(), 0) + dic.get(k.upper(), 0) for k in dic} # print(b)
集合推导式:自带结果去重功能
# set = {x**2 for x in [1, -1, 2]} # print(set) 结果是{1,4}