第六章 迭代器、生成器

1.什么是迭代器                                                      

# 引子:如何从列表、字典中取值的
#     index 索引 key ---- 只有知道索引和key的时候才能取值
#     for 循环

# 凡是可以使用for循环取值的都是可迭代的
# 可迭代协议: 内部含有__iter__方法的都是可迭代的
# 迭代器协议: 内部含有__iter__方法和__next__方法的都是迭代器

# 什么是可迭代的
# 什么是迭代器   迭代器 = iter(可迭代的),自带一个__next__方法
# 可迭代器最大的优势就是节省内存
# 当列表不断增大,随之占的内存越来越大,但当调用__iter__的时候,占用内存非常小

#
# py2 range 不管range多少 会生成一个列表 这个列表将用来存储所有的值
# py3 range 不管range多少 都不会实际的生成任何一个值
# 迭代器的优势
#     节省内存
#     快:取一个值就能进行接下来的计算,而不需要等到所有的值都计算出来才开始接下来的运算
# 迭代器的特性:惰性运算
# 列表、字典、元组、字符串、集合、range、文件句柄、enumerate

from collections import Iterable,Iterator
print(range(10000000))
# range(0, 10000000)
print(isinstance(range(1000000),Iterable))
# True # 判断是都可迭代
print(isinstance(range(1000000),Iterator))
# False # 判断是不是一个迭代器

for i in dir([1,2,3]): if i == '__iter__': print(i) # __iter__ 所以列表是可迭代的 for i in dir([1,2,3].__iter__()): if i == '__next__': print(i) # __next__ 所以list.__iter__()是迭代器

 2.next关键字                                                       

# next 打印当前值,然后准备下一个值,for循环的机制
lst_iter = [1,2,3].__iter__()
print(lst_iter.__next__())
# 1
print(lst_iter.__next__())
# 2
print(lst_iter.__next__())
# 3

# 使用while实现for循环
l = [1,2,3]

lst_iter = iter(l) # l.__iter__()
while True:
    try:
        print(next(lst_iter)) # lst_iter.__next__()
    except StopIteration:
        break
# 1
# 2
# 3

 3.生成器                                                           

# 生成器 Generator
# 自己写的迭代器,就是一个生成器
# 两种自己写生成器(迭代器)的机制:
#   生成器函数
#   生成器表达式

# 普通函数
def cloth(num):
    ret = []
    for i in range(num):
        ret.append('cloth%s'%i)
    return ret
# 生成器函数
def cloth_g(num):
    for i in range(num):
        yield 'cloth%s'%i
# 凡是带有yield的函数就是一个生成器函数
# yield功能:
#     记录当前所在的位置,等待下一次next来触发函数的状态
g = cloth_g(1000)
print(next(g))
# cloth0
print(next(g))
# cloth1
print(next(g))
# cloth2
# 说明:
# 生成器函数的调用不会触发代码的执行,
# 而是会返回一个生成器(迭代器)
# 想要生成器函数执行,需要用next
#
def func1():
    print('****')
    yield 1
func1()
# 无结果

4.文件监听                                                         

# 使用生成器监听文件输入的例子
import time
def listen_file():
    with open('userinfo') as f:
        while True:
            line = f.readline()#readline不知道停止
            if line.strip():
                yield line.strip()
            time.sleep(0.1)
g = listen_file()
for line in g:
    print(line)
# sadfasdf
# asdfasdf
# asdfasdf
# a
# b

5.send关键字                                                       

# 生成器函数,可以使用send在函数外部向函数内部传递参数,
# 可以在执行过程中添加send传值以支持后续的操作,但是不可以
# 在第一次就是用send,如果有需要可以给函数添加参数
# 想给生成器中传递值,有一个激活的过程,第一次必须要用next触发这个生成器
def func():
    print(1111)
    ret1 = yield 1
    print(2222,'ret1 :',ret1)
    ret2 = yield 2
    print(3333,'ret2 :',ret2)
    yield 3

g = func() #1- 生成一个生成器
ret = next(g)
print(ret)
# 1111
# 1
g.send('alex') # 在执行next的过程中,传递一个参数,给生成器函数的内部
# 2222 ret1 : alex

6.计算移动平均值                                                    

# 计算移动平均值
# 月度的天平均收入
# 200 300 500
# 200 250 333

def average():
    sum_money = 0
    day = 0
    avg = 0
    while True:
        money = yield avg
        sum_money += money
        day += 1
        avg = sum_money/day

g = average()
next(g)
print(g.send(200))
# 200
print(g.send(300))
# 250.0

# 运行过程
def average(): #1-
    sum_money = 0 #4-
    day = 0 #5-
    avg = 0 #6-
    while True: #13-
        money = yield avg #7- # 9- money=200
        sum_money += money #10-
        day += 1 #11-
        avg = sum_money/day #12-

g = average() #2-
next(g) #3- 触发生成器,预激活
print(g.send(200)) #8-
print(g.send(300)) #8-
print(g.send(200)) #8-

# 详细描述
def average(): # 1- 定义一个函数,看到yield,知道这个函数是生成器函数
    sum_money = 0 # 4-
    day = 0 # 5-
    avg = 0 # 6-
    while True:
        money = yield avg # 7- 执行 yield avg,将第一个avg返回给next,虽然不会打印,但是值为0
        # 9- 赋值money = 200 # 13- 执行到yield停止,并将200赋值给avg
        sum_money += money # 10- sum_money = 200
        day += 1 # 11- day = 1
        avg = sum_money/day # 12- avg 200/1 = 200,然后进行下一次循环
g = average() # 2- 代码不会执行,直接返回一个生成器g
next(g) # 3- 触发生成器的第一步 # 7-返回值为0,但是不打印
print(g.send(200)) # 8- 相当于next,触发yield --> moeny = 200 #
print(g.send(300))

7.预激生成器                                                       

# 预激生成器,因为生成器函数需要预先执行一下,才能继续执行
# 所以添加装饰器,实现预激活功能
def init(func):
    def inner(*args, **kwargs):
        ret = func(*args, **kwargs)
        next(ret) # 预激活
        return ret
    return inner

@init
def average():
    sum_money = 0
    day = 0
    avg = 0
    while True:
        money = yield avg
        sum_money += money
        day += 1
        avg = sum_money/day

g = average()
print(g.send(200))
print(g.send(200))
print(g.send(300))

8.yield from                                                       

def generator_func():
    yield from range(5) # py3简写方法
    yield from 'hello' # py3简写方法
    # for i in range(5):
    #     yield i
    # for j in 'hello':
    #     yield j
g = generator_func()

9.如何从生成器中取值                                                

# 如何从生成器中取值
# 第一种:next,随时都可以停止,最后一次会报错
print(next(g))
print(next(g))
# 第二种:for循环,从头到尾遍历一次,不遇到break、return不会停止
for i in g:
    print(i)
# 0
# 1
# 2# .....
# l
# o
# 第三种:list、tuple 数据类型的强转,会把所有的数据都加载到内存里,非常的浪费内存
print(g)
#<generator object generator_func at 0x000001774066DD00>
print(list(g))
# [0, 1, 2, 3, 4, 'h', 'e', 'l', 'l', 'o']

10.生成器小结                                                      

# 生成器函数 是我们python程序员实现迭代器的一种手段
# 主要特征是 在函数中,含有yield
# 调用一个生成器函数,不会执行这个函数中的代码,只会获得一个生成器(迭代器)
# 只有从生成器中取值的时候,才不会执行函数内部的代码,且每获取一个数据才执行得到这个数据的代码
# 获取数据的方式包括 next send 循环 数据类型的强制转化
# yield返回值的简便方法,如果本身就是循环一个可迭代的,且要把可迭代数据中的每一个匀速都返回,可以用yield from
# 使用send的时候,在生成器创造出来之后需要进行预激,这一步可以使用装饰器完成
# 生成器的特点: 节省内存 惰性运算
# 当有非常多数据需要处理的时候,就需要用到生成器
# 生成器用来解决:内存问题和程序功能之间的解耦
# 用途一:
# 例:读20000行的文件,筛选出所有带电话号码的行的内容,并将这个行的内容按照我想要的格式组合打印出来
# 比较不好的方式
# for i in 20000:
#     line
#     整理50行
#     print()
# 比较好的方式
# def func1():
#     for i in 20000:
#         yield line
# def func2():
#     内容格式化
# 用途二:
# 网络文件传输,断点续传
# 用途三:
# 函数的解耦:要将功能性的代码尽量的拆分,提高代码的可读性,提高代码的复用性
# 生成器表达式

11.列表推导式                                                      

# 列表推导式
new_lst = []
for i in range(10):
    new_lst.append(i**2)
print(new_lst)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
print([i**2 for i in range(10)])
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 利用列表推导式打印列表元素的正数
l = [1,2,3,-5,6,20,-7]
print([abs(i) for i in l])
# [1, 2, 3, 5, 6, 20, 7]
print([abs(i) for i in l if i > 0])
# [1, 2, 3, 6, 20]

# 30以内所有能被3整除的数的平方
print([i**2 for i in range(1,31) if i%3==0])
# [9, 36, 81, 144, 225, 324, 441, 576, 729, 900]
# 例三:找到嵌套列表中名字含有两个‘e’的所有名字
names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
         ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
for i in names:
    for j in i:
        if j.count('e') == 2:
            print(j)
# 用列表推导式的方法和for循环代码思路一致
print([j for i in names for j in i if j.count('e') == 2])
# ['Jefferson', 'Wesley', 'Steven', 'Jennifer']
# 比较明朗的写法
print([name for name_lst in names for name in name_lst if name.count('e')==2])
# ['Jefferson', 'Wesley', 'Steven', 'Jennifer']

# 列表推导式 排序的时候
l = [i**2 for i in range(1,31) if i%3==0]
print(l)
# [9, 36, 81, 144, 225, 324, 441, 576, 729, 900]

12.生成器表达式                                                    

# 生成器表达式 节省内存,庞大数据量的时候,使用生成器表达式
# 使用方法和列表推导式很类似
g = (i**2 for i in range(1,31) if i%3==0)
print(g) # 迭代器、生成器
# <generator object <genexpr> at 0x000002F66613DDB0>

# 面试题注意::::::::::::
# 面试题一: 一个生成器只能取一次值
# 面试题二 生成器在不找它要值的时候始终不执行
# 当它执行的时候,要以执行时候的所有变量值为准

# 面试题一: 一个生成器只能取一次值
def demo(): # 1- 定义生成器函数demo
    for i in range(4):
        yield i

g=demo() # 2- 调用demo函数,不会执行这个函数中的代码,只会获得一个生成器(迭代器)

g1=(i for i in g) # 3- 本质是利用生成器表达式创建生成器函数,不会执行这个函数中的代码,只会获得一个生成器(迭代器)
g2=(i for i in g1) # 4- 本质是利用生成器表达式创建生成器函数,不会执行这个函数中的代码,只会获得一个生成器(迭代器)

print(list(g1)) # 5- 开始取值,向g1要值,g1是一个生成器,g1向g要值,g也是一个生成器,g向demo要值,demo会给g1 -- 0,1,2,3
print(list(g2)) # 6- g2是一个生成器,向g1要值,g1向g要值,g也是一个生成器,g向demo要值,此时demo中的值已经被取完了
# [0, 1, 2, 3]
# []

# 面试题二 生成器在不找它要值的时候始终不执行
# 当它执行的时候,要以执行时候的所有变量值为准
def add(n,i):
    return n+i

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

g=test()
# 将for循环转化为非循环代码
# for n in [1,10]:
#     g=(add(n,i) for i in g)
# 转化为
# n = 1
# g=(add(n,i) for i in g)
# n = 10
# g=(add(n,i) for i in g)
# 因为生成器表达式只是创建生成器函数,***不会执行这个函数中的代码***
# 只会获得一个生成器(迭代器)g,这个g就是n=10的时候括号里for循环中的g
# 所以list(g)中的g为n=10的时候的值,下面是推导过程
n = 10
# g=(add(n,i) for i in g)
# g=(add(n,i) for i in (add(n,i) for i in test()))
# g=(add(n,i) for i in (add(n,i) for i in [0,1,2,3]))
# g=(add(10,i) for i in (add(10,i) for i in [0,1,2,3]))
# g=(add(10,i) for i in (10,11,12,13))
g=(20,21,22,23)
print(list(g))
# [20, 21, 22, 23]

猜你喜欢

转载自www.cnblogs.com/gnaix/p/8993637.html