迭代器和生成器

迭代器和生成器

  • 迭代器:

  • 楔子

  • python中的for循环

  • 迭代的概念

  • 可迭代协议和迭代器协议

  • 再谈for循环

  • 生成器:

  • 初始生成器

  • 生成器函数

 

一、迭代器---楔子

  我们都知道在对不同的数据类型的变量进行取值时可以有很多办法,比如str类型数据可以通过索引、切片或for循环的方式将所需子串取出;通过列表的索引,切片、方法或者for循环之类的都可以将列表中的元素取出;而元组呢也可以通过索引、切片、for循环的方式取出,字典可以通过key取出对应的value或者使用for循环取出key或value;而集合只能通过for循环取出里面的元素;

  通过上面各数据类型的不同取值方法,我们可以发现他们都有一个共同的取出元素的方法,那就是for循环,而在上面我并没有列出int的取值方法,为什么,因为int不能用for循环来取值,我们来看个例子,通过for循环对一个int数据类型进行循环时会发生什么情况:

>>> num = 123456
>>> for n in num:
...     print(n)
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

#报错了,提示是“类型错误:int对象不可迭代”

  不可迭代?这是什么鬼,什么是迭代呢???并且为什么其他的数据类型就可以执行并且不报错呢?for循环到底是怎样的一个机制呢?for循环是通过数据的索引取值?还是通过切片取值?还是根据什么方法来取值的呢?带着这一系列问题,我们继续往下看!

 

二、python中的for循环

  在解释迭代含义之前,我们先来谈一谈python中的for循环;要了解for循环我们还得从代码的角度出发;我们再看一些例子:

#1.首先我们对一个列表进行for循环
>>> li = [1,2,3,4,5,6]
>>> for n in li:
...     print(n,end=" ")
... 
1 2 3 4 5 6
#python成功的帮我们把列表中的元素不多不少的取出来了;

#2.我们再对一个数字进行for循环
>>> num = 123456
>>> for n in num:
...     print(n)
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
#这时,程序报错了,错误信息是int不是一个可迭代对象


#3.通过数字循环报错,我们可以逆向认为,for循环的对象可能得是一个可迭代的对象,那么想整明白for循环,看来我们要先了解一下迭代这个词了;
python中的for循环

 

三、迭代的概念

  定义:迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值;

  也就是说迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值;如果只是单纯地重复,因而不是迭代;比如:

#单纯的重复
while True: 
    print('===>') 


#迭代
l=[1,2,3]
count=0
while count < len(l):
    print(l[count])
    count+=1

 

四、可迭代协议和迭代器协议

  协议说明:假如我们自己写了一个数据类型,希望这个数据类型里的东西也可以使用for被一个一个的取出来,那我们就必须满足for的要求。这个要求就叫做“协议”。

1.什么是可迭代协议?

  定义:可以被迭代要满足的要求就叫做可迭代协议。可迭代协议的定义非常简单,就是内部实现了__iter__方法。

2.什么是可迭代对象?

  可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,也就是说只要这个对象的方法中有__iter__方法,那么我们就叫这个对象为可迭代对象;

3.如何查看某个类的某个对象的方法中有没有这个__iter__方法呢?

  我们使用一个函数来检查,dir()函数,这是一个python内置的函数,用来打印对象下的所有方法,那好我们就根据上面所说,去查一查列表、元组、字符串、集合、数字等等看看他们的方法下有没有__iter__这个方法;

#我们将各数据类型下的方法进行取交集,这样的话如果有__iter__的话说明他们都是有这个可迭代方法的;我们先对str、list、tuple、set、dict这几个数据类型进行取交集;
st = set(dir("str"))
num = set(dir(123))
boo = set(dir(True))
li = set(dir([4,5,"A"]))
tu = set(dir(("q","w",)))
dic = set(dir({"K1":"V1"}))
se = set(dir({1,6,3,9,4}))
print(st&li&tu&dic&se)
------------------- 输出结果 -----------------------------------
{'__subclasshook__', '__len__', '__gt__', '__dir__', '__sizeof__', '__doc__', '__format__', '__delattr__', '__setattr__', '__hash__', '__init_subclass__', '__iter__', '__class__', '__new__', '__str__', '__lt__', '__repr__', '__ge__', '__init__', '__le__', '__reduce__', '__ne__', '__getattribute__', '__reduce_ex__', '__contains__', '__eq__'}

#可以看出我们对这几个数据类型取交集后得到的结果是有_iter_这个方法的,说明这几个数据类型都是有这个方法的,如果有一个没有,那么这个方法是取不到的;

#接下来我们单独来看看int和bool数据类型下的方法有没有_iter_;
{'__gt__', '__reduce_ex__', 'denominator', '__ceil__', '__index__', 'numerator', '__floordiv__', '__rshift__', '__rxor__', '__add__', '__new__', '__radd__', '__le__', '__mul__', '__hash__', '__abs__', '__setattr__', '__doc__', '__rpow__', '__rtruediv__', '__and__', '__dir__', 'imag', 'bit_length', '__round__', '__ne__', '__repr__', 'conjugate', '__pow__', '__rdivmod__', 'to_bytes', '__sizeof__', '__rand__', '__divmod__', '__eq__', '__sub__', '__float__', '__rmul__', '__truediv__', '__trunc__', '__init__', '__rfloordiv__', '__neg__', '__lt__', '__delattr__', '__mod__', '__or__', '__rmod__', '__bool__', '__class__', '__rsub__', '__str__', '__xor__', 'real', '__getattribute__', '__init_subclass__', '__rlshift__', '__floor__', 'from_bytes', '__ge__', '__lshift__', '__format__', '__getnewargs__', '__int__', '__ror__', '__subclasshook__', '__rrshift__', '__pos__', '__invert__', '__reduce__'}
{'__gt__', '__reduce_ex__', 'denominator', '__ceil__', '__index__', 'numerator', '__floordiv__', '__rshift__', '__rxor__', '__add__', '__new__', '__radd__', '__le__', '__mul__', '__hash__', '__abs__', '__setattr__', '__doc__', '__rpow__', '__rtruediv__', '__and__', '__dir__', 'imag', 'bit_length', '__round__', '__ne__', '__repr__', 'conjugate', '__pow__', '__rdivmod__', 'to_bytes', '__sizeof__', '__rand__', '__divmod__', '__eq__', '__sub__', '__float__', '__rmul__', '__truediv__', '__trunc__', '__init__', '__rfloordiv__', '__neg__', '__lt__', '__delattr__', '__mod__', '__or__', '__rmod__', '__bool__', '__class__', '__rsub__', '__str__', '__xor__', 'real', '__getattribute__', '__init_subclass__', '__rlshift__', '__floor__', 'from_bytes', '__ge__', '__lshift__', '__format__', '__getnewargs__', '__int__', '__ror__', '__subclasshook__', '__rrshift__', '__pos__', '__invert__', '__reduce__'}

#我们会发现int和bool里面并没有_iter_方法
通过dir()内置函数查看对象下的方法

  因为for循环的对象必须是可迭代的,就是说这个对象必须有_iter_这个方法,for循环才会遍历,所以int类型和bool类型不能用在for循环中;

4.那么这个_iter_方法到底做了什么事情呢?为什么有了这个方法才可以进行for循环?

  我们继续通过代码去了解_iter_方法:

#我们找一个可迭代对象让其调用_iter_方法,然后再打印一下:
>>> li = [1,2]
>>> print(li.__iter__())
<list_iterator object at 0x7fc167b157f0>

  执行了print(li.__iter__()) 后,我们得到了一个list_iterator,不认识?没关系,我们找度娘问问;

  

  什么情况?一个可迭代对象执行了_iter_方法后竟然生成了一个迭代器,那么,啥是迭代器啊?

5.什么是迭代器:

  定义:迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退);

6.什么是迭代器对象:

  迭代器对象指的是内置有__iter__方法和__next__方法的对象,也就是说只要这个对象的方法中有__iter__和__next__方法,那么我们就叫这个对象为迭代器对象;

  同样我们使用dir()内置函数来查看,首先我们必须要通过可迭代对象生成一个迭代器对象,然后通过dir()这个迭代器对象来查看它下面的方法:

#我们已经知道list是一个可迭代对象,因为它方法中有_iter_,所以我们用一个列表通过自身的_iter_方法生成一个迭代器对象;
>>> li = [1,2,3]           #可迭代对象li
>>> l1 = li.__iter__()  #通过可迭代对象调用iter方法生成的迭代器对象l1
>>> print(dir(l1))         #查看这个迭代器对象下的方法;
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__']
查看迭代器对象下的方法

  通过上面的实例,我们可以知道,迭代器对象一定具有__iter__方法和__next__方法,也就是说只要具有这两个方法,我们就可以称这个对象为迭代器对象;

7.迭代器的特点:

#优点:
#1.迭代器提供了一种不依赖于索引的取值方式,这样就可以遍历那些没有索引的可迭代对象了(字典,集合,文件)
#2.迭代器与列表比较,迭代器是惰性计算的,更节省内存,惰性计算解释:惰性计算就是每次就取一个值,需要我们多次去使用它;

#缺点:
#1.一次性的,只能往后取值,不能倒着取值;
#2.无法获取迭代器的长度,使用不如列表索引取值灵活

8.迭代器的用途,多用在for循环中;

 

五、再谈for循环

  那么,我们理解了以上问题后,再来看看for循环是如何配合迭代器来获取数据的;

  for循环的内部机制:

#1.首先for循环的对象是一个可迭代对象,
#2.通过可迭代对象下的_iter_方法获取到迭代器,
#3.得到迭代器后通过迭代器的_next_方法来获取到数据
#4.将获取到的数据赋值给定义的变量;
#5.当遇到迭代器取值完毕后的报错后,for循环通过try异常处理来解决,当遇到报错也就是迭代器取值完毕后,就break退出;

  我们可以通过while循环来模拟for循环的操作流程;

>>> li = [1,2,3,4,5]               #建立一个具有可迭代的对象;
>>> l1 = li.__iter__()            #将此对象转变为迭代器对象;
>>> while 1:
...     try:
...             print(l1.__next__())   #迭代器对象使用next方法取值
...     except StopIteration:
...             break                       #取值完毕后遇到异常退出
... 
1
2
3
4
5

##这样的形式是不是很像for循环那样,其实就是for循环内部实现的机制,不同的是for循环将每次去取到的值进行了变量赋值了;

 

六、初识生成器

  我们知道的迭代器有两种:1.直接调用next方法返回的,2.通过可迭代对象执行iter方法得到;

  迭代器好处就是可以节省内存;

  如果在某些情况下,也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器;

生成器创建:

  1.生成器函数

  常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

  2.生成器表达式

  类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表;

生成器Generator:

  本质:生成器的本质就是迭代器,所以生成器本省自带了iter和next

  特点:和迭代器一样,惰性运算,节省内存,只能向前,开发者可以自定义;

生成器函数:

  定义:一个包含yield关键字的函数就是一个生成器函数;

  yield说明:yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。

import time
def genrator_fun1():
    a = 1
    print('现在定义了a变量')
    yield a
    b = 2
    print('现在又定义了b变量')
    yield b

g1 = genrator_fun1()
print('g1 : ',g1)       #打印g1可以发现g1就是一个生成器
print('-'*20)   #我是华丽的分割线
print(next(g1))
time.sleep(1)   #sleep一秒看清执行过程
print(next(g1))

---------------------- 输出结果 ------------------------
g1 :  <generator object genrator_fun1 at 0x00000000021EF728>
--------------------
现在定义了a变量
1
现在又定义了b变量
2
初识生成器函数

生成器的好处。

  就是不会一下子在内存中生成太多数据,就是节省内存;

  比如:我想让工厂给学生做校服,生产2000000件衣服,我和工厂一说,工厂应该是先答应下来,然后再去生产,我可以一件一件的要,也可以根据学生一批一批的找工厂拿。
而不能是一说要生产2000000件衣服,工厂就先去做生产2000000件衣服,等回来做好了,学生都毕业了。。。

#初识生成器二

def produce():
    """生产衣服"""
    for i in range(2000000):
        yield "生产了第%s件衣服"%i

product_g = produce()
print(product_g.__next__()) #要一件衣服
print(product_g.__next__()) #再要一件衣服
print(product_g.__next__()) #再要一件衣服
num = 0
for i in product_g:         #要一批衣服,比如5件
    print(i)
    num +=1
    if num == 5:
        break

#到这里我们找工厂拿了8件衣服,我一共让我的生产函数(也就是produce生成器函数)生产2000000件衣服。
#剩下的还有很多衣服,我们可以一直拿,也可以放着等想拿的时候再拿

初识生成器二
初识生成器二

使用生成器写一个监听文件输入例子:

import time
def tail(filename):
    f = open(filename)
    f.seek(0, 2) #从文件末尾算起
    while True:
        line = f.readline()  # 读取文件中新的文本行
        if not line:
            time.sleep(0.1)
            continue
        yield line

tail_g = tail('tmp')
for line in tail_g:
    print(line)
生成器监听文件输入的例子

send关键字

  含义:和next方法一样也是取下一个值,不同的是,send还可以给上一个yield的所在位置进行传值;

  注意:不能在开头使用send,因为没有上一个yield,也不能在最后一个yield使用send,因为已经到结尾了;

def generator():
    print(123)
    content = yield 1
    print('=======',content)
    print(456)
    yield2

g = generator()
ret = g.__next__()
print('***',ret)
ret = g.send('hello')   #send的效果和next一样
print('***',ret)

#send 获取下一个值的效果和next基本一致
#只是在获取下一个值的时候,给上一yield的位置传递一个数据
#使用send的注意事项
    # 第一次使用生成器的时候 是用next获取下一个值
    # 最后一个yield不能接受外部的值
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count

g_avg = averager()
next(g_avg)
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))
计算移动平均值

yield from

def gen1():
    for c in 'AB':
        yield c
    for i in range(3):
        yield i

print(list(gen1()))

def gen2():
    yield from 'AB'
    yield from range(3)

print(list(gen2()))

--------------- 输出打印 -----------------------------------------
['A', 'B', 0, 1, 2]
['A', 'B', 0, 1, 2]
yield from

 

posted @ 2018-08-10 17:15  重启试试  阅读(264)  评论(0编辑  收藏  举报