12.python迭代器与生成器

迭代器

双下方法:很少直接调用的方法, 一般情况下,是通过其他语法触发的。
迭代协议:内部实现了iter方法;
迭代器遵循迭代器协议:必须拥有iter方法和next方法。
迭代对象可以通过调用iter()方法能得到一个迭代器;
迭代器的特点:
1. 方便使用,且只能取所有的数据一次;
2. 节省内存空间。

"""
dir([1, 2])是列表中实现的所有方法,都是以列表的形式返回给我们的,
dir([1, 2].__iter__())是迭代器中实现的所有方法
set(dir([1, 2]))是转换成集合
然后取差集,就可以看到迭代器特有的方法了
"""
print(set(dir([1,2].__iter__())) - set(dir([1,2])))
iter_1 = [1, 2, 3, 4, 5, 6].__iter__()
#一个一个的取值,在for循环中,就是在内部调用了__next__方法才能取到一个一个的值。
# next取到迭代器中没有元素时会抛出一个异常StopIteration.
iter_1.__next__()
#根据索引值指定从哪里开始迭代
iter_1.__setstate__(1)
#获取迭代器中元素的长度
iter_1.__length_hint__()
  1. range()方法
print('__next__' in dir(range(12)))
print('__iter__' in dir(range(12)))
from collections import Iterator
print(isinstance(range(12), Iterator))
输出:
False
True
False

可以看出range()不是一个迭代器。

  1. 为什么要有for循环
    基于上面讲的列表这一大堆遍历方式,聪明的你立马看除了端倪,于是你不知死活大声喊道,你这不逗我玩呢么,有了下标的访问方式,我可以这样遍历一个列表啊
l=[1,2,3]

index=0
while index < len(l):
    print(l[index])
    index+=1

#要毛线for循环,要毛线可迭代,要毛线迭代器

没错,序列类型字符串,列表,元组都有下标,你用上述的方式访问,perfect!但是你可曾想过非序列类型像字典,集合,文件对象的感受,所以嘛,年轻人,for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的iter方法将其转换成一个迭代器,然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了,而且你看到的效果也确实如此,这就是无所不能的for循环,觉悟吧,年轻人

生成器

1. 初识生成器

  • 我们知道的迭代器有两种:一种是调用方法直接返回的,一种是可迭代对象通过执行iter方法得到的,迭代器有的好处是可以节省内存。
  • 如果在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器。

3. python中提供的生成器有两种:

  • 1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
  • 2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

生成器的本质:就是迭代器,自带了iternext方法;
生成器的特点:惰性运算,开发者自定义。

4. 生成器函数

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

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

ge = generator_func1() # 打印g1可以发现g1就是一个生成器
print('g1: ', ge)
print('-' * 20)
print(next(ge))
time.sleep(1)
print(next(ge))
#输出
g1:  <generator object generator_func1 at 0x00000274FDBA96D8>
--------------------
现在定义了a变量:
1
现在又定义了b变量:
2

1. 特点:

  1. 调用的时候函数不执行,返回一个生成器;
  2. 调用next方法的时候会取到一个值;
  3. 直到取完最后一个,在执行next会报错。

2. 监听文件输入例子:

def tail(file_name):
    with open(file_name) as fn:
        fn.seek(0, 2)
        while True:
            line = fn.readline()
            if not line:
                time.sleep(1)
                continue
            yield line

tail_g = tail('test.txt')
for line in tail_g:
    print(line)

3. send

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

g = generator()
ret = next(g) # or g.__next__()
print('----', ret)
ret = g.send('hello')
print('----', ret)

说明:

  1. send 在获取下一个值的效果与next一致,但是在使用send之前先要用next。
  2. send可以传一个值给yield之前的变量。
  3. 最后一个yield不能接收外部的值

4.示例

  1. 计算移动平均值
def averager():
    total = 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(50))
# 输出:
10.0
20.0
30.0
  1. 协程装饰器实现计算移动平均值
def init(func):
    def inner(*args, **kwargs):
        g = func(*args, **kwargs)
        next(g)
        return g
    return inner

@init
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count

g_avg = averager()
print(g_avg.send(10))
print(g_avg.send(20))
print(g_avg.send(30))

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]
从生成器中取值的几种方法:
  1. next()
  2. for
  3. 数据类型强制转换 list(gen1()) ,不推荐,占用内存。

生成器表达式

laomuji = ('鸡蛋%s' %i for i in range(10))
print(laomuji)
print(next(laomuji))
print(laomuji.__next__())
print(next(laomuji))
# 输出:
鸡蛋0
鸡蛋1
鸡蛋2

总结:

  1. 把列表解析的[]换成()得到的就是生成器表达式
  2. 列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
  3. Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和
sum(x ** 2 for x in range(4))

而不用多此一举的先构造一个列表:

sum([x ** 2 for x in range(4)])

3. 总结:

可迭代对象:
  拥有iter方法
  特点:惰性运算
  例如:range(),str,list,tuple,dict,set
迭代器Iterator:
  拥有iter方法和next方法
 例如:iter(range()),iter(str),iter(list),iter(tuple),iter(dict),iter(set),reversed(list_o),map(func,list_o),filter(func,list_o),file_o

生成器Generator:
  本质:迭代器,所以拥有iter方法和next方法
  特点:惰性运算,开发者自定义
使用生成器的优点:

1.延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。

#列表解析
sum([i for i in range(100000000)])#内存占用大,机器容易卡死
 
#生成器表达式
sum(i for i in range(100000000))#几乎不占内存

2.提高代码可读性

4. 常见面试题

面试题1:

def demo():
    for i in range(4):
        yield i

g=demo()

g1=(i for i in g)
g2=(i for i in g1)

print(list(g1))
print(list(g2))
# 输出:
1. [0, 1, 2, 3]
  []
2. 如果print(list(g1))注释后输出 [0, 1, 2, 3]
3. 生成器中的值只能取一次。
import os

def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

@init
def list_files(target):
    while 1:
        dir_to_search=yield
        for top_dir,dir,files in os.walk(dir_to_search):
            for file in files:
                target.send(os.path.join(top_dir,file))
@init
def opener(target):
    while 1:
        file=yield
        fn=open(file)
        target.send((file,fn))
@init
def cat(target):
    while 1:
        file,fn=yield
        for line in fn:
            target.send((file,line))

@init
def grep(pattern,target):
    while 1:
        file,line=yield
        if pattern in line:
            target.send(file)
@init
def printer():
    while 1:
        file=yield
        if file:
            print(file)

g=list_files(opener(cat(grep('python',printer()))))

g.send('/test1')

协程应用:grep -rl /dir

tail&grep
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342