yiruia的个人博客分享 http://blog.sciencenet.cn/u/yiruia

博文

Day14 迭代器和生成器

已有 1032 次阅读 2020-3-5 10:02 |系统分类:科研笔记

一、迭代器

1、引子

l=[1,2,3]

取值的方式有:索引取值、for循环取值,对于for 循环取值方式:可被循环的数据类型有:list、dict、str、set、tuple、f=open()句柄,range(),enumerate枚举。

2、概念

(1)查看某种数据类型中所有方法的方式

print(dir([])) #告诉我列表拥有的所有方法
print(dir({})) #告诉我集合拥有的所有方法
print(dir('')) #告诉我字符串拥有的所有方法
print(dir(())) #告诉我元组拥有的所有方法
print(dir(range(10))) #告诉我range拥有的所有方法
set = set(dir([]))&set(dir({}))&set(dir(''))
print(set) #输出列表、字典、字符串的共有方法,其中均包含__iter__方法

(2)验证某种数据类型是否可循环的方式:

print('__iter__' in dir(int)) # False
print('__iter__' in dir(list)) # True
print('__iter__' in dir(dict)) # True
print('__iter__' in dir(set)) # True

得出结论:只要能被for循环的数据类型,就一定拥有__iter__方法。

(3)一个列表执行了__iter__()方法之后的返回值就是一个迭代器

print([].__iter__()) # <list_iterator object at 0x00000293238CA108>


print(set(dir([].__iter__())) - set(dir([]))) # {'__length_hint__', '__setstate__', '__next__'}
  • __length_hint__() #计算元素个数

print([12].__iter__().__length_hint__()) # 元素个数
  • __next__() #从迭代器中一个一个取值

l = [1,2,3]
iterator = l.__iter__() #生成一个迭代器
print(iterator.__next__())
print(iterator.__next__())
print(iterator.__next__())
print(iterator.__next__())

得出结论:

  • 能被for循环的都是iterable,可迭代的-->__iter__。只要含有__iter__()方法都是可迭代的。

  • [].__iter__()生成一个迭代器-->.__next__通过next可以从迭代器中一个一个的取值

  • 只要含有__iter__()方法的数据类型都是可迭代的——可迭代协议

  • 在for循环时,第一件事就是找是否存在__iter__()方法,若不存在此方法就报错。

2、迭代器的概念

图片.png


可以被for循环的都是可迭代的,可迭代的内部都有__iter__方法;可迭代器一定可迭代,但可迭代的不一定是迭代器,要看他是否含有__next__方法。可迭代器中的.__iter__()方法可得到一个迭代器,__next__()方法可以一个一个的获取值

  • 可迭代对象包含迭代器

  • 如果一个对象拥有__iter__方法,是可迭代对象;如果一个对象拥有next方法,其就是迭代器

  • 生成器是一种特殊的迭代器,生成器自动实现了“迭代器协议”,即(__iter__和__next__方法)

3、for循环

只有是可迭代对象的时候才能用for,当我们遇到一个新的变量,不确定能否使用for循环的时候,就判断它是否可迭代。

for i in l:
    pass
iterator = l.__iter__()
print(iterator.__next__())

当报错时,迭代终止。

迭代器的好处:

(1)从容器类型中一个一个的取值,会把所有的值都取到。

(2)可以节省内存空间。迭代器不会在内存中占用一大块内存,而是随着循环每次生成一个,每次next每次给我一个。

# 生产200万个字符串。随意取值,可用迭代器
def func():
    for i in range(2000000):
        i = 'wahaha%s'%i
print(range(1000000)) # range(0, 1000000) #可迭代对象,并不是一次性生成的,而是一个一个给的
print(list(range(100000))) #得到一个从0到99999的列表
l = [1,2,3,4,5]
iterator = l.__iter__()
while True:
    print(iterator.__next__()) #执行完后,自动抛出异常。

在执行时,迭代器最好,for循环替用户做了,不需要用户再__iter__(),__next__()方法了。

3、为什么列表、字典、字符串等是可迭代的,但不是迭代器。

这是因为python的迭代器对象表示的是一个数据流,迭代器对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出stopiteration错误。可以把这个数据流看作是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过__next__()函数实现按需计算下一个数据,所以iterator的计算是惰性的,只要在需要返回下一个数据时它才会计算。

二、生成器——由迭代器而来

生成器函数——本质上就是我们自己写的函数

1、只要含有yield关键字的函数都是生成器函数,yeild关键字只能写在函数里,且yeild和return不能同时存在。

(1)生成器表达式

列表推导式: 
  sum([i+1 for i in range(1000000000)]) 前面是一个表达式,表示结果,后面跟一个for加一个可迭代的对象,再后面还可以跟# 一个if else 语句进行判断
生成器表达式:
   sum((i+1 for i in range(1000000000)))

(2)生成器函数,带yield的函数

生成器时一个特殊的函数,可以用作控制循环的迭代行为,python中生成器时迭代器的一种,使用yield代替return返回,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。

def myList(num):  # 定义生成器
    now = 0  # 当前迭代值,初始为0    while now < num:
        val = (yield now)  # 返回当前迭代值,并接受可能的send发送值;yield在下面会解释
        now = now + 1 if val is None else val  # val为None,迭代值自增1,否则重新设定当前迭代值为val

生成器函数执行后会得到一个生成器作为返回值。

def generator():
    print(1)
    yield 'a'
# 生成器函数:执行之后会得到一个生成器作为返回值
ret = generator()
print(ret)

'''
<generator object generator at 0x0000021859F797C8>

'''
def generator():
    print(1)
    yield 'a'
# 生成器函数:执行之后会得到一个生成器作为返回值
ret = generator()
print(ret)
print(ret.__next__()) # 生成器就是一个迭代器
'''
<generator object generator at 0x0000021859F797C8>
1
a
'''

2、yield和return的区别

解释一:

就像打电玩一样,你蓄力发大招的时候,如果执行了return,就直接把大招发出去了,蓄力结束,如果执行了yield,就相当于返回一个生成器对象,每调用一次next(),就发一个大招。

解释二:

return是函数返回值,当执行到return,后续的逻辑代码不再执行

yield是创建迭代器,可以用for来遍历,有点事件触发的意思。

def generator():
    print(1)
    yield 'a'
    print(2)
    yield 'b'
g = generator() # g为生成器,generator()为生成器函数
ret = g.__next__()
print(ret)
'''
1
a
'''
def generator():
    print(1)
    yield 'a'
    print(2)
    yield 'b'
    yield 'c'
g = generator() # g为生成器,generator()为生成器函数
ret = g.__next__()
print(ret)
ret = g.__next__()
print(ret)
ret = g.__next__()
print(ret)
'''
1
a
2
b
c
'''
def generator():
    print(1)
    yield 'a'
    print(2)
    yield 'b'
    yield 'c'
g = generator() # g为生成器,generator()为生成器函数
for i in g:
    print(i)
'''
1
a
2
b
c
'''
#哇哈哈%i
def wahaha():
    for i in range(20000):
        yield '哇哈哈%s'%i
g = wahaha()
count = 0
for i in g:
    count += 1
    print(i)
    if count>5:#从生成的函数中取5个值
        break
print('****', g.__next__()) # 如果需要的话,还可以接着再取
'''
哇哈哈0
哇哈哈1
哇哈哈2
哇哈哈3
哇哈哈4
哇哈哈5
**** 哇哈哈6
'''

3、为什么列表、字典、字符串等是可迭代的,但不是迭代器。

这是因为python的迭代器对象表示的是一个数据流,迭代器对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出stopiteration错误。可以把这个数据流看作是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过__next__()函数实现按需计算下一个数据,所以iterator的计算是惰性的,只要在需要返回下一个数据时它才会计算。

l = [1,2,3,4]
for i in l:
    print(i)
    if i == 2:
        break
for i in l:
    print(i) #循环结束后再次执行函数,从头开始
def wahaha():
    for i in range(20000):
        yield '哇哈哈%s'%i
g = wahaha() #g,g1相当于两个迭代器,各走各的。
g1 = wahaha()
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g1.__next__())
'''
哇哈哈0
哇哈哈1
哇哈哈2
哇哈哈0
'''
def tail(filename):
    f = open('d:\\huangshurui.txt',encoding='utf-8')
    while True:
        line = f.readline()
        if line:
            print('****',line.strip('\r\n'))
tail('huangshurui.txt')
'''
**** 22111
**** 月儿
**** xinger
**** 月儿
**** 星儿
**** nihao
**** 噢噢噢噢 哦哦
'''

针对以上函数,我想实现在每行前面加上***后打印,除此之外还想在在每行加上。。。后打印,解决此问题,可采用生成器函数

def tail(filename):
    f = open('d:\\huangshurui.txt',encoding='utf-8')
    while True:
        line = f.readline()
        if line:
            yield line.strip('\r\n')

g = tail('huangshurui.txt')
for i in g:
    if '月儿' in i: #监听过滤的效果
        print('****',i)

三、迭代器和生成器小结

(一)迭代器

  1. 双下方法__inter__(),__next__():很少直接调用的方法,一般情况下是通过其他语法促发的。

  2. 可迭代的——含有__inter__()的方法,判断是否含有__inter__方法的语法:'__inter__' in dir(数据类型)

  3. 可迭代器协议:含有__inter__和__next__方法

  4. 迭代器一定是可迭代的,而可迭代的不一定是迭代器,可迭代的通过调用__iter__()方法就可以得到一个迭代器。

  5. 迭代器的优点:

    很方便使用,且只能从头到尾的取数据

    节省内存空间,按照需要取数据

(二)生成器

1、生成器的本质就是迭代器

2、生成器的表现形式:

  • 生成器函数

  • 生成器表达式

(1)生成器函数

  1. 含有yield关键字的函数就是生成器函数

  2. 特点:调用函数的时候函数不执行,返回一个生成器;每次调用next方法时会取到一个值,直到取完最后一个,再执行next会报错

(2)从生成器中取值的几个方法

  1. next

  2. for

  3. 数据类型的强制转换list(g),但是它不够好,因为太过于占内存




https://blog.sciencenet.cn/blog-3405644-1221869.html

上一篇:Day13 作业
下一篇:Day15生成器函数进阶
收藏 IP: 223.91.46.*| 热度|

0

该博文允许注册用户评论 请点击登录 评论 (0 个评论)

数据加载中...

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2024-5-20 05:43

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部