Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the wp-pagenavi domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/blog.zhujinhui.net/wp-includes/functions.php on line 6114

Notice: 函数 _load_textdomain_just_in_time 的调用方法不正确twentyseventeen 域的翻译加载触发过早。这通常表示插件或主题中的某些代码运行过早。翻译应在 init 操作或之后加载。 请查阅调试 WordPress来获取更多信息。 (这个消息是在 6.7.0 版本添加的。) in /var/www/blog.zhujinhui.net/wp-includes/functions.php on line 6114
面试必问(2):迭代器和生成器是什么?有什么区别? – 煅魂-JeffreyChu的修炼屋

面试必问(2):迭代器和生成器是什么?有什么区别?

迭代器(Iterator)和生成器(Generator)都是 Python 中与迭代操作相关的概念,但它们在功能和使用上有所不同。

概念

迭代器(Iterator)
  • 迭代器是一种对象,它允许你逐一遍历某个数据集合。一个对象是迭代器,必须实现两个方法:__iter__()__next__()
  • __iter__() 返回迭代器对象本身,使得对象可以用于循环遍历。
  • __next__() 在每次调用时返回集合中的下一个元素。如果没有元素可以返回,则会抛出 StopIteration 异常。
    你可以通过调用 iter() 函数来获取对象的迭代器,例如:iter(my_list) 将返回一个列表 my_list 的迭代器。

注意:可迭代对象(iterable) 和迭代器(Iterator)是不同概念,可迭代对象(iterable)是没有实现 __next__()方法的,但有__iter__() ,所以像list、dict、str这些都不是迭代器,而是可迭代对象。

下面用一段代码展示下迭代器和生成器

# a是可迭代对象
a = [i for i in range(20)]
# iter(a)才是迭代器
it_dir = dir(iter(a))
# 检查iter(a)迭代器是否含有__iter__和__next__
print("__iter__" in it_dir and "__next__" in it_dir)


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


# g是迭代器,也是生成器
g = generator()
g_dir = dir(g)
# 检查g生成器(迭代器)是否含有__iter__和__next__
print("__iter__" in g_dir and "__next__" in g_dir)

# Outputs
# True
# True
生成器(Generator):
  • 生成器是一个特殊类型迭代器,用于生成序列。它是通过函数来定义的,但与普通函数不同,它使用 yield 关键字来返回值。
  • 当生成器函数被调用时,它并不立即执行函数体,而是返回一个生成器对象。这个生成器对象在每次调用其 __next__() 方法时,执行生成器函数体中的代码,直到遇到 yield,然后暂停执行并返回 yield 后面的值。
  • 生成器可以保存函数的局部状态,因此当函数被再次调用时,它从上一次暂停的地方继续执行。
两者区别:
  • 定义方式:迭代器是通过类来定义并实现 __iter__()__next__() 方法;生成器是通过使用 yield 关键字的函数来定义的。不过生成器实际上是一种特殊的迭代器。
  • 使用方式:迭代器需要通过实现类来定义和管理数据迭代,而生成器通过函数的代码执行和 yield 表达式自动生成数据。
  • 内存效率:生成器通常更节省内存,因为它们会根据需要产生值,而不是一次性生成所有值。

应用

迭代器和生成器在 Python 中都有重要的作用,主要用于数据的迭代、处理和生成。它们在编写可读性和内存效率更高的代码方面特别有用。

大数据处理/惰性计算

当你处理大量数据时,使用生成器可以避免将整个数据集加载到内存中,有些数据需要惰性计算(延迟计算),从而节省内存。例如,逐行读取大文件或者逐个处理大型数据集。

比如统计大文件有多少行:

def read_large_file(file_path):
    """使用生成器逐行读取大型文件。"""
    try:
        with open(file_path, 'r') as file:
            for line in file:
                # 在这里使用yield暂停并返回每一行
                yield line.strip()  # 去除每行的换行符
    except FileNotFoundError:
        print(f"文件 {file_path} 未找到。")
        return

def process_large_file(file_path):
    """示例函数,用于演示如何使用生成器处理大型文件。"""
    line_count = 0  # 行计数器
    for line in read_large_file(file_path):
        # 对每一行进行处理
        # 这里可以添加你自己的处理逻辑
        print(f"Processing line {line_count}: {line}")
        # 示例:简单地统计行数
        line_count += 1
    print(f"Total lines processed: {line_count}")

if __name__ == "__main__":
    # 在这里指定要读取的文件路径
    file_path = "large_file.txt"
    # 使用process_large_file函数处理大文件
    process_large_file(file_path)
流式数据处理:

生成器适用于流式数据处理,数据流源源不断地产生,你可以通过生成器逐一处理它们。例如,读取数据流(如网络流或数据库流)并逐一处理。

比如下面这段源源不断从redis队列拉取数据:

import time
import redis

def redis_stream_generator(redis_client, queue_name):
    """生成器函数,用于从 Redis 队列中流式获取数据。"""
    while True:
        # 尝试从 Redis 队列中弹出一个数据项
        data = redis_client.blpop(queue_name, timeout=2)
        if data:
            # `data` 是一个元组 (queue_name, data)
            _, value = data
            # 使用 yield 返回数据
            yield value
        else:
            # 如果队列为空,睡眠2秒
            time.sleep(2)

def process_redis_stream(data_stream):
    """处理 Redis 数据流的示例函数。"""
    for data in data_stream:
        # 对数据进行处理
        print(f"Processing data: {data}")

        # 在这里添加你的数据处理逻辑
        # 例如:对数据进行统计、计算等

        # 如果要在特定条件下停止处理,可以在这里添加逻辑

if __name__ == "__main__":
    # 连接到 Redis 服务器
    redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
    # 定义 Redis 队列的名称
    queue_name = 'my_queue'
    # 创建 Redis 数据流生成器
    data_stream = redis_stream_generator(redis_client, queue_name)
    # 处理 Redis 数据流
    process_redis_stream(data_stream)
无限序列:

生成器可以用来生成无限序列,例如斐波那契数列、素数序列等。这些序列往往无法通过列表来表示,因为它们是无限的,但可以通过生成器逐
一生成和处理。

def fibonacci_generator():
    """生成器函数,用于生成斐波那契数列。"""
    a, b = 0, 1
    while True:
        # 使用 yield 返回当前的斐波那契数
        yield a
        # 计算下一个斐波那契数
        a, b = b, a + b


def process_fibonacci_sequence(n):
    """示例函数,用于演示如何处理生成的斐波那契数列。"""
    # 创建 Fibonacci 数列的生成器
    fibonacci_gen = fibonacci_generator()
    print(f"First {n} numbers in the Fibonacci sequence:")
    # 使用生成器逐个获取前 n 个斐波那契数
    for _ in range(n):
        fibonacci_number = next(fibonacci_gen)
        print(fibonacci_number)


if __name__ == "__main__":
    # 指定要生成的斐波那契数的个数
    n = 10  # 例如,获取前 10 个斐波那契数
    # 处理并打印前 n 个斐波那契数
    process_fibonacci_sequence(n)
组合生成器:

通过生成器的组合,你可以创建复杂的数据流水线。例如,将多个生成器组合在一起,进行数据的提取、转换和加载(ETL)操作。

协程和异步编程:

在 Python 的异步编程中,生成器和 async、await 等关键字结合使用,用于实现异步操作和协程。

比如下面这段异步批量采集某个api数据

import aiohttp
import asyncio

async def fetch_url(url):
    """
    异步函数,用于发送网络请求并返回响应的内容。
    """
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            # 获取响应内容并返回
            content = await response.text()
            return content

async def main():
    """
    主函数,演示如何并发发送多个网络请求。
    """
    # 定义一组要请求的 URL 列表
    urls = [
        'http://xmishu.zhujinhui.net/api/hot_words',
        'http://xmishu.zhujinhui.net/api/hot_words',
        'http://xmishu.zhujinhui.net/api/hot_words',
    ]

    # 创建任务列表,使用列表推导式创建多个异步任务
    tasks = [fetch_url(url) for url in urls]

    # 使用 asyncio.gather 运行所有任务,并等待它们完成
    results = await asyncio.gather(*tasks)

    # 输出所有请求的结果
    for i, result in enumerate(results):
        print(f"Response from URL {urls[i]}:")
        print(result[:100])  # 只打印前100个字符

# 使用 asyncio.run 来运行主函数
if __name__ == '__main__':
    asyncio.run(main())

总结

迭代器是通过类来定义并实现 __iter__()__next__() 方法,生成器是一个使用 yield 关键字来返回值的特殊函数,也是一种特殊类型迭代器,它们在处理一些大数据问题的处理上都更能节省内存。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注