Python学习之-迭代器和生成器

前言:

在Python中,迭代器(Iterator)和生成器(Generator)是实现迭代协议的对象,用于遍历集合中的元素。它们之间有联系,但也有一些关键的差异。

1 迭代器(Iterator)

概念:迭代意味着重复多次,就像循环一样。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,它遵循迭代协议,即必须实现两个方法:
iter():返回迭代器自身。
next():返回容器的下一个元素,如果没有元素了,抛StopIteration异常。也可以使用内置函数next,next(it)和it .next()等效。
下面是使用迭代器的一个基本示例:

# 创建一个简单的列表
my_list = [1, 2, 3]
# 获取迭代器对象
my_iter = iter(my_list)
# 使用迭代器遍历元素
print(next(my_iter)) # 输出 1
print(next(my_iter)) # 输出 2
print(next(my_iter)) # 输出 3
# 下一次调用将会抛出 StopIteration 异常,因为列表中没有更多的元素了
# print(next(my_iter))

1.1 可迭代对象(Iterable)

可以被用作如for循环之中对象通常都是可迭代的。其中一些包括:
所有的序列类型,如:list,str,tuple。
集合类型,如:set,frozenset。
映射类型,如:dict。
文件对象和其他满足迭代协议的对象(即实现了 iter 方法或者 getitem 方法)。

1.2 不可迭代对象

那些不支持迭代协议的对象都是非迭代对象,常见的有:
数字类型:int,float,complex 等。
None。
自定义的对象,除非在类中实现了 itergetitem 方法。

#!/usr/bin/env python
# coding=utf-8
# Author: Summer
# Time: 2024.04.3

# [1.list]
# 列表是可迭代的
my_list = [1, 2, 3]
for item in my_list:
    print(item)
    '''
    会依次打印 1, 2, 3
    '''
# [2.string]
# 字符串也是可迭代的
my_string = "hello"
for char in my_string:
    print(char)
    '''
    会依次打印 'h', 'e', 'l', 'l', 'o'
    '''
# [3.dict]
# 字典是可迭代的,迭代的是它的键
my_dict = {'a': 1, 'b': 2}
for key in my_dict:
    print(key)
    '''
    # 会依次打印 'a', 'b'
    '''

# [4.int]
# 数字是非可迭代的
my_number = 10
print(my_number.__iter__)
'''
Traceback (most recent call last):
  File "D:\PycharmProjects\debug_test\debug.py", line 34, in 
    print(my_number.__iter__)
AttributeError: 'int' object has no attribute '__iter__'
'''

# [5.float]
# 浮点类型是非可迭代的
float_number = 0.34
print(float_number.__iter__)
'''
Traceback (most recent call last):
  File "D:\PycharmProjects\debug_test\debug.py", line 45, in 
    print(float_number.__iter__)
AttributeError: 'float' object has no attribute '__iter__'
'''


# [6.自定义对象]
# 自定义类的对象默认也是非可迭代的
class MyClass:
    pass


# 创建 MyClass 的一个实例
my_obj = MyClass()
# 下面的for循环会抛出TypeError,除非在MyClass类中实现了__iter__或__getitem__方法
for item in my_obj:
    print(item)

'''
Traceback (most recent call last):
  File "D:\PycharmProjects\debug_test\debug.py", line 61, in 
    for item in my_obj:
TypeError: 'MyClass' object is not iterable
'''

2 迭代器对象

迭代器对象是实现了迭代器协议的对象,这意味着它们支持两个方法:iter()和__next__()。迭代器对象代表了一个数据流,你可以通过不断调用__next__()方法来逐一访问流中的项目,直到没有更多数据时抛出StopIteration异常。通过__iter__()方法,迭代器返回它自己,这使得迭代器对象也可以被用在支持迭代的上下文中,如for循环中。
迭代器的核心特性是它们是惰性的,只有在你请求下一个值时,才会计算和提供值,而不是一开始就计算所有的值。这种特性特别适合处理大数据集,因为它们可以帮你节省内存。
实现一个迭代器对象
下面是一个简单例子,展示了如何实现一个迭代器对象。这个例子是一个计数器,它可以迭代从给定的起始值开始,直到指定的结束值。

#!/usr/bin/env python
# coding=utf-8
# Author: Summer
# Time: 2024.04.3

class Counter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def __next__(self):
        if self.current > self.high:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1


# 使用 Counter 迭代器
counter = Counter(1, 3)

for num in counter:
    print(num)
# 输出:
# 1
# 2
# 3

在这个例子中,Counter类实现了__iter__方法和__next__方法,因此它是一个迭代器。它启动时从low开始,通过每次调用__next__时增加current属性的值,并在达到high值时停止。
每次迭代时,__next__方法被调用,若当前值超过最高值high,则抛出StopIteration异常,以此来结束迭代。否则,返回当前值,并准备下一个值。for循环自动处理这些细节,使得使用迭代器非常简单直观。

3 迭代器的优缺点

基于索引的迭代取值,所有迭代的状态都保存在了索引中
而基于迭代器实现迭代的方式不再需要索引
所有迭代的状态就保存在迭代器中
然而这种处理方式优点与缺点并存
优点:
内存效率:迭代器仅在需要时计算或"生成"下一元素,这意味着它们不必在内存中存储整个数据集。这使得迭代器非常适合处理大型数据集。
惰性计算:迭代器使用惰性计算,意味着它们一次只产生一个元素,只有当下一元素需要时才被计算。这有助于提高性能并减少计算资源的使用。
统一的迭代接口:Python 中的迭代器协议提供了一致的迭代接口。因此,使用相同的构造,如 for 循环和迭代器函数(iter() 和 next()),可以迭代多种数据结构。
链式操作:由于迭代器本质上是流,它们可以组合或链接起来执行复杂的查询和操作,类似于 Unix 中的管道。
无需创建额外的容器:有时,你可能只需要一次访问元素,并且不需要保存它们。迭代器使得可以在没有额外的列表、集合或其他容器的情况下遍历元素。
缺点:
单向性:迭代器只能前进,不能后退,也不能重置。一旦被消费,你需要重新创建迭代器来再次遍历内部元素。
非随机访问:迭代器不支持随机或索引访问。如果你需要随机访问,可能需要使用列表或数组等其他数据结构。
状态不变性:迭代器是有状态的,这意味着一旦迭代器的状态改变,比如你用next()取出下一个元素,它的内部计数器将改变,无法返回到之前的状态。
不直观的长度计算:与列表或元组不同,你不能直接获取迭代器的长度,因为迭代器是惰性的,它们的元素是按请求生成的。
错误处理:如果不正确使用迭代器或不熟悉迭代器的异常处理(例如,处理StopIteration异常),可能会引入错误。
理解迭代器的这些优缺点对于高效地使用它们非常重要。利用迭代器的优点可以帮助你书写内存高效和清晰简洁的代码,而避免它们的局限则可以帮助你避免性能问题和潜在的错误。在决定是否使用迭代器时,考虑你的特定用例和数据处理需求是至关重要的。

4 生成器

生成器(Generators)是 Python 中一种特殊类型的迭代器,你可以把它们看作是可以记住执行位置的函数。当一系列值需要按需计算时,生成器提供了一种非常高效的方式。生成器使用 yield 语句来生成一个值序列,而不是一次性地产生一个完整的数据集并返回。
生成器的特点包括:
与迭代器类似,生成器也支持惰性计算,它们一次只生成一个项目。
生成器函数在每次产生输出之后,其状态会被挂起,直到下一次使用 next() 函数或 for 循环请求下一个值。
当生成器函数执行完成时,或者遇到无法再产生新值的条件时,会抛出 StopIteration 异常。
使用生成器函数定义生成器
生成器是通过在函数中使用 yield 关键字来定义的。每当 yield 语句运行时,它会返回一个值,并在此时挂起函数的状态,包括局部变量、指针位置等。下一次调用时,它会从中断点继续执行。

4.1 简单生成器函数示例:

#!/usr/bin/env python
# coding=utf-8
# Author: Summer
# Time: 2024.04.3

def count_up_to(max):
    count = 1
    while count <= max:
        yield count
        count += 1


# 创建生成器
counter = count_up_to(5)
# 使用 next() 获取值
print(next(counter))  # 输出 1
print(next(counter))  # 输出 2

# 可以继续调用 next() 获取剩余的值,或者使用 for 循环
for number in counter:
    print(number)  # 输出 3 4 5

在这个示例中,生成器函数 count_up_to 接受参数 max 并计数到这个值。每次 yield 被调用,它就会产生当前的计数值,并在下一次调用时从上次离开的地方继续执行。

4.2 生成器表达式示例:

#!/usr/bin/env python
# coding=utf-8
# Author: Summer
# Time: 2024.04.3
# 使用生成器表达式创建生成器

gen_exp = (x * x for x in range(4))

# 使用 for 循环迭代生成器
for num in gen_exp:
    print(num)  # 依次打印 0, 1, 4, 9

生成器表达式看起来类似于列表推导,但使用圆括号而不是方括号。它们非常适合于简单的场景,其中一个完整的生成器函数可能显得过于冗长。

4.3 yield和next详解

当我们谈论生成器在 Python 中的工作原理时,两个关键概念是 yield 关键字和 next() 函数。它们共同定义生成器的行为,使得生成器既能生成值也能暂停和恢复执行。
yield 关键字
yield 关键字用于定义生成器函数中的一个位置,这里会产出(yield)一个值给调用者,并暂停函数的执行,直到下一次通过 next() 来请求另一个值。
当生成器函数中执行到一个 yield 表达式时,它会返回该表达式后面的值给生成器的调用者,并暂停执行(所有局部变量和状态都会被保留)。
yield 的挂起的函数执行可以在下一次调用 next() 或迭代时继续从上次暂停的位置恢复。
yield 的使用示例:

#!/usr/bin/env python
# coding=utf-8
# Author: Summer
# Time: 2024.04.3


def simple_generator():
    print("Stage 1")
    yield 1

    print("Stage 2")
    yield 2

    print("Stage 3")
    yield 3


gen = simple_generator()

# 每次调用 next() 时,生成器从上次暂停的地方开始,直到遇到下一个 yield
val = next(gen)  # 输出 "Stage 1",返回 1
val = next(gen)  # 输出 "Stage 2",返回 2

next() 函数
next() 函数是用于与迭代器或生成器交互的内置函数,用于从迭代器或生成器中获取下一个元素。
当你对生成器调用 next() 时,生成器函数就会执行,直到它遇到下一个 yield 语句。
如果生成器函数完成全部逻辑并退出而没有遇到额外的 yield,或者直接遇到一个 return 语句,则会抛出 StopIteration 异常,表示迭代已经完成。
next() 的使用示例:

#!/usr/bin/env python
# coding=utf-8
# Author: Summer
# Time: 2024.04.3


def simple_generator():
    print("Stage 1")
    yield 1

    print("Stage 2")
    yield 2

    print("Stage 3")
    yield 3


gen = simple_generator()

# 使用 next() 遍历生成器
try:
    while True:
        value = next(gen)
        print(value)
except StopIteration:
    pass
'''
输出:
Stage 1
1
Stage 2
2
Stage 3
3
'''

这个示例中,simple_generator 生成器函数被 next() 逐步执行,直至 StopIteration 异常抛出,表示没有更多元素可供产出。
迭代器协议与 yield
生成器利用 yield 实现了迭代器协议,这意味着生成器自动支持 iter() 和 next() 方法。因此,你可以在任何期望迭代器的地方使用生成器,如 for 循环、list() 函数等。

gen = simple_generator()

# 使用 for 循环自动处理 StopIteration 异常
for value in gen:
    print(value)  # 会依次打印 1, 2, 3

4.4 生成器的特点

内存效率:由于生成器按需生成值,它们可以用于无法放在内存中的大型数据集。
改善性能:惰性计算意味着只有在必要时才计算值,这可以减少不必要的计算,从而节省计算资源。
简洁性:使用生成器和生成器表达式可以使代码更简洁易懂。
适用于管道处理:生成器可以被链式结合,用于构建复杂的数据管道。
总的来说,生成器是处理流式数据、大型数据集或者复杂序列等问题的一种强大而高效的工具。利用生成器,我们可以使代码变得更加高效,同时还能降低内存消耗。

总结:

迭代器和生成器都允许你对一系列数据进行迭代,但生成器提供了一种更简便,内存高效的方式来实现迭代器的功能。当你需要一个简单的迭代器并且不想实现类时,生成器是一个非常好的选择。适用场合包括处理大数据流、文件处理、网络通信等等。

你可能感兴趣的:(Python,python,学习,迭代器,生成器)