python函数-迭代器和生成器

Posted 鸿飞漫天

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python函数-迭代器和生成器相关的知识,希望对你有一定的参考价值。

一 迭代器

1.1 认识迭代器

什么是迭代 什么是迭代器

迭代器
如何从列表、字典中取值的
    index索引 ,key
    for循环
凡是可以使用for循环取值的都是可迭代的
可迭代协议 :内部含有__iter__方法的都是可迭代的
迭代器协议 :内部含有__iter__方法和__next__方法的都是迭代器 

一个生成器 只能取一次
生成器在不找它要值的时候始终不执行
当他执行的时候,要以执行时候的所有变量值为准
l = [1,2,3]
while True:
    lst = l.__iter__()   ## iter 方法自带一个next方法,所以第一次可以取值输出,并取到下一个值保存
    print(next(lst))


l = [1,2,3]
lst = l.__iter__()          ##当输出最后一个值的时候,仍旧执行next方法会报错
while True:
    print(next(lst))

l = [1,2,3]
lst = l.__iter__()
while True:
    try:
        print(next(lst))     #  测试错误
    except StopIteration:    #   当遇到报错  StopIteration  的时候,执行break,停止执行next方法。


break

什么是迭代器  迭代器 = iter(可迭代的),自带一个__next__方法

可迭代 最大优势是 节省内存

我们可以从range学习

from collections import Iterable,Iterator   #可迭代的 迭代器
print(range(100000000))
print(isinstance(range(100000000),Iterable))   # True 是可迭代的
print(isinstance(range(100000000),Iterator))    # False  不是迭代器
py2 range 不管range多少 会生成一个列表 这个列表将用来存储所有的值
py3 range 不管range多少 都不会实际的生成任何一个值

迭代器的优势:
节省内存
取一个值就能进行接下来的计算 ,而不需要等到所有的值都计算出来才开始接下来的运算 —— 快
迭代器的特性:惰性运算

二 生成器

生成器,Genenator

2.1 什么是生成器

自己写的迭代器,就是一个生成器

写迭代器的两种机制:

生成器函数和生成器表达式

# 凡是带有yield的函数就是一个生成器函数
def func():
    print(****)
    yield 1
    print(^^^^)
    yield 2   # 记录当前所在的位置,等待下一次next来触发函数的状态
生成器函数的调用不会触发代码的执行,而是会返回一个生成器(迭代器)
想要生成器函数执行,需要用next
g = func()   # g = 函数执行之后生成的一个生成器
print(‘---‘,g)  # 函数执行不输出任何内容
print(--,next(g))   # 调用一次next,会执行一次取值,到下一次遇到yield停止
print(--,next(g))

调用两次next返回的结果:

****
-- 1
^^^^
-- 2

2.1.2使用生成器监听文件输入

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)

# 使用mac的时候,需要用echo向文件追加内容测试。win手动输入 使用ctrl+s保存即可

 

2.1.3 send关键字

理解send

def func():
    print(11111)
    ret1 = yield 1
    print(22222,ret1 :,ret1)
    ret2 = yield 2
    print(33333,ret2 :,ret2)
    yield 3

g = func()
ret = next(g)  # 第一次next,返回11111 和 yield 1 。并记录当前位置,等待下一个next来触发函数状态     
print(ret)
print(g.send(alex))  # 在执行next的过程中 传递一个参数 给生成器函数的内部,同时接收yield的返回值。
print(g.send(金老板))

##接收send发送的参数的是上次次结束的yield。第一次执行next(g),后,返回yield 1。yield会记录当前所在位置,等待下一个next来触发函数的状态。当执行send(alex)的时候的时候,相当于又执行一个next,且yield 1首先执行,接收send的内容。
# send 不能在第一次next之前。第一次必须用next触发这个生成器,有一个激活的过程

    send  不能用在生成器的第一步。必须先用next触发激活生成器

例子: 计算移动平均值

要求,每次给你一个数,都计算之前所有拿到的数的平均值。例如计算月度的天平均收入

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.__next__())  #第一步 先触发生成器,之后才能send
print(g.send(20))
print(g.send(40))
print(g.send(60))

 

2.2 预激生成器

个人理解:生成器send的时候需要用next激活,才能使用send传值。这里用到的是装饰器来进行生成器函数的预激活

def init(func):
    def inner(*args,**kwargs):
        ret = func(*args,**kwargs)
        next(ret)   # 预激活
        return ret  #  需要返回生成器的结果给send
    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(300))
print(g.send(600))

 

2.3 生成器的使用

2.3.1 yield from

# yield from
def generator_func():
    for i in range(5):
        yield i
    for j in hello:
        yield j

def generator_func():
    yield from range(5)
    yield from hello


以上两种写法完全是完全相同的

 

2.3.2 迭代器只能生成一次

我们看下这两段代码

# 定义函数
# # yield from
def generator_func():
    for i in range(5):
        yield i

# 代码调用第一种方式
g = generator_func()
while True:
    print(next(g))

#输出内容:
0
1
2
3
4
  File "/Users/wph/PycharmProjects/learn/Day4/8.复习练习.py", line 194, in <module>
    print(next(g))
StopIteration

# 代码调用第二种方式
while True:
    print(next(generator_func()))

# 执行结果是一直输出 0

 我们想一下,为什么会出现这样的情况呢?

来继续看下面的代码:

def generator_func():
    for i in range(5):
        yield i

g1 = generator_func()
g2 = generator_func()
print(g1,g2)
#输出结果 <generator object generator_func at 0x104c2b0a0> <generator object generator_func at 0x104c2b0f8> 可以看到 g1和g2的内存地址是不一样的。也就是说每次调用generator_func,都会生成一个全新的生成器。那么每次从里面取值,都是会从头开始的。

 

2.3.3 生成器函数定义

生成器函数 是我们python程序员实现迭代器的一种手段

主要特种是 在函数中 含有 yield

调用一个生成器函数 不会执行这个函数中的代码,知识会获得一个生成器(迭代器)

只有从生成器中取值的时候,才会执行函数内部的代码。且每获取一个数据才执行得到这个数据的代码

获取数据的方式包括 next send 循环 数据类型的强制转换

yield返回值的简便方法,如果本身就是一个可迭代的,且要把可迭代数据中的每一个元素都返回,可以用 yield from

使用send的时候,在生成器创造出来自后要进行预激,这一步可以使用装饰器完成。

生成器的特点:节省内存 惰性计算

生成器用来解决内存问题和程序功能之间的解耦

 

2.3.4 生成器表达式

先了解一下列表推导式:

# 列表推导式
new_lst = []
for i in range(10):
    new_lst.append(i**2)
print(new_lst)

print([i**2 for i in range(10)])

# 求列表中的数对2取余
l = [1,2,3,-5,6,20,-7]
print([i%2 for i in range(10)])

# 取出列表中的奇数
l = [1,2,3,-5,6,20,-7]
print([num for num in l if num%2 == 1])

30以内所有能被3整除的数
print([i for i in range(30) if i%3 ==0])

30以内所有能被3整除的数的平方
print([i**2 for i in range(30) if i%3 ==0])


找到嵌套列表中名字含有两个‘e’的所有名字
names = [[Tom, Billy, Jefferson, Andrew, Wesley, Steven, Joe],
         [Alice, Jill, Ana, Wendy, Jennifer, Sherry, Eva]]
print([name for name_lst in names for name in name_lst if name.count(e) == 2])

 

 

生成器表达式

l = [1,2,3,-5,6,20,-7]
30以内所有能被3整除的数

l = [i for i in range(30) if i%3 ==0]   # 列表推导式 排序的时候
g = (i for i in range(30) if i%3 ==0)   # 生成器表达式 庞大数据量的时候 使用生成器表达式   #与列表推导式相比,只是把[]换成了()
print(l) #结果是一个列表
print(g) ## 结果只是拿到一个生成器  <generator object <genexpr> at 0x1043100a0>
for i in g:print(i)  # 从这个生成器中取值

 

总结:

1. 把列表解析(列表推导式)的[]换成()得到的就是生成器表达式

2. 列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存。

3. Python不但使用迭代器协议,让for循环更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如,sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所有,我们可以直接这样计算一系列值的和

print(sum(x ** 2 for x in range(4)))

 

 

2.3.5 生成器面试题

 # 面试题  请确认这段代码的打印内容
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))  #执行生成器表达式  #结果  [0,1,2,3]  
print(list(g2))                  #结果  []

解析:因为g=demo()的使用调用生成器函数,g是一个生成器。g1 是一个生成器推导式,并不会执行。

当print从g1 中取值完成后,由于一个生成器只能取值一次。,g2取不到值了。

 

面试题2: 很蛋疼的,慢慢看吧 ,不信你能懂。。(重点在于 生成器是什么时候执行的)

# 判断下列代码输出内容

def add(n,i):
    return n+i

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

g=test()
for n in [1,3,10]:
    g=(add(n,i) for i in g)

print(list(g))

 

 输出内容

10 11 12 13

 

 

2.3.5 生成器准则

一个生成器 只能取值一次。

生成器在不找他要值的时候,始终不执行

当他执行的时候,要以执行时的所有变量值为准  

 














以上是关于python函数-迭代器和生成器的主要内容,如果未能解决你的问题,请参考以下文章

Python迭代器和生成器,装饰器

python编程系列---可迭代对象,迭代器和生成器详解

python函数-迭代器和生成器

Python笔记·第十二章—— 函数 迭代器和生成器

Python初探第二篇-装饰器和迭代器,生成器

Python/迭代器和生成器