Python:迭代器、生成器【使用了yield的函数称为生成器】【生成器是一个返回迭代器的函数】【用List遍历数据会一次性加载所有数据,占用内存太大;生成器可以分批次向内存加载数据】

一、迭代器

顾名思义,迭代器就是用于迭代操作(for 循环)的对象,它像列表一样可以迭代获取其中的每一个元素。

python3中任何实现了 _next_ 方法的对象都可以称为迭代器。

迭代器与列表的区别在于,构建迭代器的时候,不像列表把所有元素一次性加载到内存,而是以一种延迟计算(lazy evaluation)方式返回元素,这正是它的优点。

比如列表含有中一千万个整数,需要占超过400M的内存,而迭代器只需要几十个字节的空间。因为它并没有把所有元素装载到内存中,而是等到调用 next 方法时候才返回该元素(按需调用 call by need 的方式,本质上 for 循环就是不断地调用迭代器的next方法)。

以斐波那契数列为例来实现一个迭代器:

class Fib:
    def __init__(self, n):
        self.prev = 0
        self.cur = 1
        self.n = n

    def __next__(self):
        if self.n > 0:
            value = self.cur
            self.cur = self.cur + self.prev
            self.prev = value
            self.n -= 1
            return value
        else:
            raise StopIteration()


f = Fib(5)
print(type(f))
print(f.__next__())
print(f.__next__())
print(next(f))
print(f.__next__())
print(next(f))
print(f.__next__())

打印结果:

<class '__main__.Fib'>
1
1
2
3
5
Traceback (most recent call last):
  File "test03.py", line 25, in <module>
    print(f.__next__())
  File "test03.py", line 15, in __next__
    raise StopIteration()
StopIteration

Process finished with exit code 1

二、生成器

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

调用一个生成器函数,返回的是一个迭代器对象

为什么用这个生成器,是因为如果用List的话,会占用更大的空间,比如说取0,1,2,3,4,5,6............100000000

在python2中,你可能会这样:

for i in range(100000000):
    a=i

这个时候range(100000000)就默认生成一个含有100000000个数的list了,所以很占内存。

这个时候你可以用刚才的yield组合成生成器进行实现:

  • 第一种写法:

    def foo(num):
        print("starting...")
        while num < 10:
            num = num + 1
            yield num
    
    
    print(type(foo))
    
    f = foo(0)
    print(f)
    print('------------------------------')
    print(f.__next__())
    print(f.__next__())
    print(f.__next__())
    

    打印结果:

    <class 'function'>
    <generator object foo at 0x000001B9F5130750>
    ------------------------------
    starting...
    1
    2
    3
    
    Process finished with exit code 0
    
  • 第二种写法:

    def foo(num):
        print("starting...")
        while num < 10:
            num = num + 1
            yield num
    for n in foo(0):
        print(n)
    

    打印结果:

    starting...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

在python2中也可以用xrange(1000)这个生成器实现 yield 组合:xrange(100000000)

for n in xrange(100000000):
    a=n

其中要注意的是python3时已经没有xrange()了,在python3中,range()就是xrange()了,你可以在python3中查看range()的类型,它已经是个<class ‘range’>了,而不是一个list了,毕竟这个是需要优化的。

三、生成器用法

1、yield

首先,如果你还没有对yield有个初步分认识,那么你先把yield看做“return”,这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了。看做return之后再把它看做一个是生成器(generator)的一部分(带yield的函数才是真正的迭代器),好了,如果你对这些不明白的话,那先把yield看做return,然后直接看下面的程序,你就会明白yield的全部意思了:

def foo():
    print("------------------- 进入foo()函数 -------------------")
    for i in range(10):
        print('第{0}次循环: 开始'.format(i))
        res = yield i
        print("res:", res)
        print('第{0}次循环: 结束'.format(i))


g = foo()
print('g = ', g)
print('********************* 开始执行 *********************')
print(next(g))
print("----------------------------------------")
print(next(g))
print("----------------------------------------")
print(next(g))
print("----------------------------------------")
print(next(g))

打印结果:

g =  <generator object foo at 0x000001CB3B2677C8>
********************* 开始执行 *********************
------------------- 进入foo()函数 -------------------0次循环: 开始
0
----------------------------------------
res: None0次循环: 结束
第1次循环: 开始
1
----------------------------------------
res: None1次循环: 结束
第2次循环: 开始
2
----------------------------------------
res: None2次循环: 结束
第3次循环: 开始
3

Process finished with exit code 0

代码运行顺序如下:

  • 程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器generator(相当于一个对象)

  • 直到调用next方法,foo函数正式开始执行,先执行foo函数中的print("------------------- 进入foo()函数 -------------------")方法,打印结果:

    ------------------- 进入foo()函数 -------------------
    
  • 进入for循环

  • 执行for循环里的代码print('第{0}次循环: 开始'.format(i)),然后遇到yield关键字,然后yield想象成returnreturn了一个0之后,程序停止,并没有执行赋值给res操作,此时 next(g)语句执行完成;打印结果:

    0次循环: 开始
    0
    
  • 程序执行print("----------------------------------------"),打印结果:

    ----------------------------------------
    
  • 开始执行接下来的print(next(g))

  • 这个时候和上面那个差不多,不过不同的是,这个时候是从上一个next()程序停止的地方开始执行的(即:继续从上一次yield代码处向下执行代码),也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,程序继续在for执行,直到又一次碰到yield,打印结果:

    res: None0次循环: 结束
    第1次循环: 开始
    1
    
  • 程序执行print("----------------------------------------"),打印结果:

    ----------------------------------------
    
  • 从上一个next()程序停止的地方开始执行的,即:继续从上一次yield代码处向下执行代码,直到又一次碰到yield,打印结果:

    res: None1次循环: 结束
    第2次循环: 开始
    2
    
  • 直至for循环结束;

到这里你可能就明白yield和return的关系和区别了,带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。

2、send

再看一个这个生成器的send函数的例子,这个例子就把上面那个例子的最后一行换掉了

def foo():
    print("------------------- 进入foo()函数 -------------------")
    for i in range(10):
        print('第{0}次循环: 开始'.format(i))
        res = yield i
        print("res:", res)
        print('第{0}次循环: 结束'.format(i))


g = foo()
print('g = ', g)
print('********************* 开始执行 *********************')
print(next(g))
print("----------------------------------------")
print(next(g))
print("----------------------------------------")
print(next(g))
print("----------------------------------------")
print(g.send(7))

输出结果:

g =  <generator object foo at 0x00000227A3B667C8>
********************* 开始执行 *********************
------------------- 进入foo()函数 -------------------0次循环: 开始
0
----------------------------------------
res: None0次循环: 结束
第1次循环: 开始
1
----------------------------------------
res: None1次循环: 结束
第2次循环: 开始
2
----------------------------------------
res: 72次循环: 结束
第3次循环: 开始
3

Process finished with exit code 0

send函数的概念:send是发送一个参数给res的,因为上面讲到,return的时候,并没有把 i i i 赋值给 r e s res res,下次执行的时候只好继续执行赋值操作,只好赋值为None了,而如果用 send 的话,开始执行的时候,先接着上一次 yield i 执行,先把 7 7 7 赋值给了 r e s res res,然后执行 next 的作用,遇见下一回的 yield,return 出结果后结束。

  • 程序执行g.send(7),程序会从yield关键字那一行继续向下运行,send会把7这个值赋值给res变量
  • 由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次进入for循环
  • 程序执行再次遇到yield关键字,yield会返回后面的值后,程序再次暂停,直到再次调用next方法或send方法。

四、案例

1、重复调用生成器

def data_iter():
    for i in [1, 2, 3]:
        yield i


for i in range(5):
    for a in data_iter():
        print("a = ", a)
    print('\n')

打印结果:

a =  1
a =  2
a =  3


a =  1
a =  2
a =  3


a =  1
a =  2
a =  3


a =  1
a =  2
a =  3


a =  1
a =  2
a =  3



Process finished with exit code 0



参考资料:
python中yield的用法详解——最简单,最清晰的解释

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中的迭代器生成器是一种基于惰性计算的概念,它们可以有效地处理大量的数据或者无限序列。下面我将别介绍迭代器生成器迭代器(Iterator)是一个实现了迭代协议(Iterator Protocol)的对象。通过调用内置函数 `iter()` 可以将可迭代对象转换为迭代器迭代器对象可以使用内置函数 `next()` 来逐个访问数据元素,直到没有更多的元素可供访问时,引发 `StopIteration` 异常。例如,可以使用迭代器遍历列表、元组、集合等容器类型的数据生成器(Generator)则是一种特殊的迭代器。它不需要显式地实现迭代协议,而是通过函数中的 `yield` 关键字来实现惰性计算。生成器函数在每次调用时返回一个值,并在下一次调用时从上一次离开的位置继续执行。这样可以节省大量的内存空间,并且提高程序的效率。生成器函数定义时使用 `def` 关键字,并包含至少一个 `yield` 关键字。 下面是一个简单的示例代码,演示了如何使用迭代器生成器: ```python # 使用迭代器遍历列表 my_list = [1, 2, 3, 4, 5] my_iter = iter(my_list) while True: try: item = next(my_iter) print(item) except StopIteration: break # 使用生成器生成斐波那契数列 def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b fib = fibonacci() for i in range(10): print(next(fib)) ``` 希望以上解释能够帮助你理解迭代器生成器的概念。如果有任何进一步的问题,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值