python中的迭代器

⛳ 目录

  • 什么是迭代器
  • 如何生成迭代器
  • 迭代器的用法

🎈迭代器和可迭代对象

迭代器是为了给迭代对象进行迭代使用的。迭代也就是遍历,可以从头到尾的遍历所有的元素。列表、集合、元组、字典、字符串、都是可迭代对象。如果一个对象拥有_iter方法,其是可迭代对象。

🎈生成器

  • 生成器(Generator)在 Python 中具有许多有用的作用和优势,包括:
  1. 惰性计算(Lazy Evaluation):生成器使用惰性计算的方式逐个生成值。它们不会一次性生成所有值,而是按需生成,节省了内存和计算资源。这对于处理大型数据集或无限序列特别有用。

  2. 延迟执行(Lazy Execution):生成器的执行是逐步的,只在需要时才会执行代码。这种延迟执行的特性使得生成器非常适合处理耗时的操作,避免了一次性计算所有结果。

  3. 迭代器协议支持:生成器自动实现了迭代器协议,因此它们可以被 for 循环等迭代工具直接使用。这使得生成器可以方便地进行迭代遍历,无需额外的代码。

  4. 内存效率:生成器不需要存储所有生成的值,而是在需要时按需生成。这使得生成器非常适合处理大型或无限序列,因为它们节省了内存资源。

  5. 状态维护:生成器函数可以维护局部变量和状态,通过 yield 语句可以保存函数的执行状态,并在下一次调用时恢复执行。这使得生成器可以实现复杂的迭代逻辑、状态机和协程等。

  6. 数据流处理:生成器可以用于处理连续的数据流,逐个处理数据并生成结果。这对于处理实时数据或流式数据非常有用,可以逐步处理数据而无需等待所有数据到达。

  7. 管道和链式操作:生成器可以通过管道和链式操作组合在一起。一个生成器的输出可以作为另一个生成器的输入,从而实现数据流的处理和转换。

总的来说,生成器提供了一种高效、灵活和内存友好的方式来处理迭代和序列生成。它们可以用于处理大数据集、实现延迟计算、处理数据流、实现协程等各种场景。通过生成器,可以以优雅而高效的方式处理迭代逻辑,减少内存占用,并提高代码的可读性和可维护性。

🎈什么是迭代器

  • 迭代器(Iterator)是一种特殊的对象,它可以在遍历时逐个返回元素。迭代器实现了迭代协议,包括两个重要的方法:
  1. __iter__() 方法:返回迭代器对象自身。
  2. __next__() 方法:返回迭代器的下一个元素,如果没有元素可供返回,则抛出 StopIteration 异常。

迭代器对象可以通过调用内置的 iter() 函数来获取,它可以应用于可迭代对象(如列表、元组、字符串等)或自定义的实现了 __iter__() 方法的对象。

  • 按需加载,有一点内容就放入内存一点
  • 无需等待所有内容加载到内存中才使用,提升效率
  • 在说什么是迭代器之前,我们来举一个例子。首先有请我们的小木同学。小木同学想去超市买苹果,到了超市,看到超市中有苹果的柜台。不过柜台上销售员正在摆放苹果,小木同学要等到销售员摆放苹果完毕之后才可以拿。这就很耽误小木同学的时间。其实。柜台上已经有苹果了,但是销售不让拿,非要等到全部摆放完成之后才可以。接下来第二天,小木同学又来超市买苹果。这次的销售员很好说话,他说,我可以一边摆放苹果,一边让小木来拿。这样就可以很好的节省小木同学的时间,如果小木同学拿苹果的速度超过了销售员摆放的速度,小木同学只需要等待一会儿,只要销售员摆放一个苹果,小木同学就可以立即拿到。这样大大增加了小木同学买苹果的效率。迭代器就类似于后者。
  • 我们平时的程序。都是一次性写入到内存中,比如我们的列表有100或者1000甚至1万个数据,都是一次性写入到内存里的,通过这样让我们来使用。但是迭代器是按需加载,有一点内容就放到内存里一点,我们就可以立刻使用内存中的一点数据进行逻辑处理。这样就不需要等待所有的内容都写到内存中,我们就可以使用,大大提升了效率。

🎈如何生成迭代器-iter

  • 介绍:

    • 生成一个迭代器对象
  • 用法:

    • iter(iterable)
    • 我们将iter函数中传入一个列表,它会返回一个列表的迭代器对象。生成迭代器对象之后,我们将使用这个迭代器对象。通过Python的内置函数。就可以将迭代器对象中的数据进行返回。我们来看一下用法。我们只需要调用next函数,将迭代器对象作为参数传入。只要我们的迭代器对象当中有数据,我们可以一直调用next函数。用法有些类似于我们的文件对象读取的readline函数,我们来看一个例子。我们定义了一个迭代器对象
  • 参数介绍:

    • iterable :可迭代的数据类型
  • 举例:

    • iter([1, 2, 3])
  • 返回值:

    • < list_ iterator at 0x4f3aee0>

🎈迭代器的使用-next

  • 介绍:
    • 返回迭代器中数据
    • 我们每使用一次next函数,就会返回迭代器中的一个值
    • 比如括弧里边传入的是一个列表。看似我们将这个列表所有的内容都放到迭代器里,实际上则不然。当我们每调用一次next的时候,我们才会把列表中的一个元素放到内存中,由next去读取。所以我们的iter并没有一次性的把列表中的数据全部放到迭代器中,这就是我们说的按需加载。
      -迭代器对象中的数据全部调用完毕,再次调用next函数,程序就会直接报个错误。
  • 用法:
    • next(iterator)
  • 参数介绍:
    • iterator :迭代器对象
  • 举例:
    • iter_ obj = iter([1,2,3])
    • next(iter_obj)
  • 返回值:
    • 1, 2 ,3
    • StopIteration

🎈迭代器常用方法之生成迭代器

  1. for循环生成法一yield
    • yield 是一个关键字,用于定义生成器函数(Generator Function)
    • 生成器函数是一种特殊的函数,它在执行时会生成一个可迭代的对象,通过逐步生成值的方式,而不是一次性生成所有值的方式。生成器函数使用 yield 语句来定义生成器的每个值,并在生成器中暂停和恢复执行。
    • 以下是生成器函数的语法示例:
def my_generator():
    # 生成器函数的逻辑
    yield value1
    yield value2
    # ...
  • 在生成器函数中,每个 yield 语句都会将一个值生成给调用者,并且函数的执行会在此处暂停。调用者可以通过迭代生成器对象来获取生成器函数产生的值。每次迭代,生成器函数会从上一次暂停的位置恢复执行,并生成下一个值。

  • 以下是一个简单的生成器函数示例,生成斐波那契数列的前n个数字:

def fibonacci(n):
    a, b = 0, 1
    count = 0
    while count < n:
        yield a
        a, b = b, a + b
        count += 1

# 使用生成器函数生成斐波那契数列的前10个数字
fib_gen = fibonacci(10)
for num in fib_gen:
    print(num)
  • 生成器函数 fibonacci 使用 yield 语句生成每个斐波那契数列的数字。在每次迭代中,生成器函数会在 yield 处暂停,并返回生成的值。在 for 循环中,我们迭代生成器对象 fib_gen,每次迭代都会从上一次暂停的位置恢复执行,并生成下一个斐波那契数。

  • 生成器表达式可以直接生成一个生成器对象,而不需要定义生成器函数。

总结起来,yield 关键字用于定义生成器函数中,它允许逐步生成值的方式来创建生成器。通过 yield,可以在生成器函数中暂停和恢复执行,并向调用者提供生成的值。这种逐步生成值的方式在处理大量数据或需要延迟计算的情况下非常有用。

  • 我们先看第一种。我们可以在函数中使用for循环。并对每一个for循环的成员都要用yield的函数。它的意思就是将for循环中的每个成员放到一个迭代器对象中。不过只有被调用才会被放入,我们来看例子。我们看到这个例子。其中在定义了一个test函数里边使用for循环。我们将每个循环的成员通过yield放到迭代器中。当然,就像我们刚才说的,只有在调用next函数的时候,for循环的成员才会通过yield关键字放到迭代器中被next调用。这就是按需加载的意思。
In [27]: def test():
for i in range(10):
......print(i)
......yield i
In [28]: res = test()
In [29]: next(res)
0ut[29]: 0
  1. for循环一行生成迭代器
    • 第二种方法,我们可以直接使用for循环生成一个迭代器对象。我们来看这样一句话,我们将for循环放到了一行中,我们看for i in[123],这句话和我们定义一般的for循环没有差距,但是本来在for循环代码块中执行的i成员却跑到了for关键字的前面。并且将整个for循环包含for关键字前面的i成员用小括号包裹起来。这就是一个用for循环在非函数中定义迭代器的方法。我们将这个迭代器赋值给变量res,并使用next调用迭代器执行迭代器中的内容。使用for循环生成的迭代器,可以不使用next也可以执行。我们依然可以通过for循环来获取迭代其中的数据。不仅如此,当我们执行调取完迭代器中的数据之后,程序不会抛出异常。相比于next函数要友好的多。
In [30]:res=(i for i in [1,2,3])
In [31]: next(res)
Out [31] : 1

🎈迭代器常用方法之for循环获取

  • 优点:不会报错
In [32]:res=(i for i in [1,2,3])
In [33]: for item in res:
     ...:    print ( item)
1
2
3

PS: 迭代器每一次使用会将其放到内存中,将数据被读取后,内存的数据会被释放,所以当完整读取一个迭代器后,这个迭代器中的内存数据就已经空了,再次执行就拿不到数据了。

🎯实战

  • 当抛出了StopIteration这样一个异常.实际上在我们的工作中就必须要去捕获这个异常
  • 首先书写一个封装一层的next函数,我们加一个横杠,_next里边选入我们的iter_obj对象。我们通过try-except来进行捕获,try中我们直接return,我们调用_next,并且把我们的iter生成器传入,然后,我们要用except,在里边我们通过StopIteration声明要捕获的异常类型,我们并不需要将我们的StopIteration起一个别名,因为我们知道它一定会抛什么样的错误。
  • 我们的迭代器会把数据一次一次都放到内存中。当放到内存中并且被读取之后,内存的内容就会被释放,所以我们当完整的读取完一个迭代其对象之后。这个迭代器对象中的内存数据就已经空了。所以我们再去执行就拿不到数据了,请同学们要注意这一点。
  • 除了我们刚才说的按需加载可以提升我们的执行速度以外,还有另外一个原因,想一想。如果我们有一个列表,现在只有十个数据,那么读取速度会很快,可能对资源,也就是说我们的内存占用不会消耗很大。如果我们有一个100亿成员的列表,那么这100亿的数据都要一次性写到内存里。我们想一下这个时间将消耗有多大?不仅如此,一百亿的数据写到内存里,是不是有可能会撑爆我们的内存?因为毕竟我们的内存是有容量限制的。当超过了我们的容量,我们的内存就会溢出,程序就会报错了。所以如果通过迭代器的方式,我们只需要用到一个数据,我们就把这个数据扔到内存里并且被使用,这样子既可以提高效率,又可以节省内存的消耗。这就是我们平时使用迭代器的目的。
#!/user/bin/env python
# -*- coding: utf-8 -*-

iter_obj = iter((1, 2, 3))

def _next(iter_obj):
    try:
        return next(iter_obj)
    except StopIteration:
        return None

# print(_next(iter_obj))
# print(_next(iter_obj))
# print(_next(iter_obj))
# print(_next(iter_obj))  # 当迭代器中数据被调用完后,将不再被调用

for i in iter_obj:
    print(i)
print('========')

def make_iter():
    for i in range(10):
        yield i

iter_obj = make_iter()

for i in iter_obj:
    print(i)
print('---------')
"""
迭代器每一次使用会将其放到内存中,
将数据被读取后,内存的数据会被释放,
所以当完整读取一个迭代器后,这个迭代器
中的内存数据就已经空了,再次执行就拿不
到数据了
"""
for i in iter_obj:
    print(i)

iter_obj = (i for i in range(5))

for i in iter_obj:
    print(i)
print('========')
for i in iter_obj:
    print(i)
1
2
3
========
0
1
2
3
4
5
6
7
8
9
---------
0
1
2
3
4
========

Process finished with exit code 0

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2023年11月7日
下一篇 2023年11月7日

相关推荐