面试必问(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 关键字来返回值的特殊函数,也是一种特殊类型迭代器,它们在处理一些大数据问题的处理上都更能节省内存。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容