面試必問(2):迭代器和生成器是什么?有什么區(qū)別?

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

概念

迭代器(Iterator)
  • 迭代器是一種對象,它允許你逐一遍歷某個數(shù)據(jù)集合。一個對象是迭代器,必須實現(xiàn)兩個方法:__iter__()__next__()。
  • __iter__() 返回迭代器對象本身,使得對象可以用于循環(huán)遍歷。
  • __next__() 在每次調(diào)用時返回集合中的下一個元素。如果沒有元素可以返回,則會拋出 StopIteration 異常。
    你可以通過調(diào)用 iter() 函數(shù)來獲取對象的迭代器,例如:iter(my_list) 將返回一個列表 my_list 的迭代器。

注意:可迭代對象(iterable) 和迭代器(Iterator)是不同概念,可迭代對象(iterable)是沒有實現(xiàn) __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):
  • 生成器是一個特殊類型迭代器,用于生成序列。它是通過函數(shù)來定義的,但與普通函數(shù)不同,它使用 yield 關鍵字來返回值。

  • 當生成器函數(shù)被調(diào)用時,它并不立即執(zhí)行函數(shù)體,而是返回一個生成器對象。這個生成器對象在每次調(diào)用其 __next__() 方法時,執(zhí)行生成器函數(shù)體中的代碼,直到遇到 yield,然后暫停執(zhí)行并返回 yield 后面的值。

  • 生成器可以保存函數(shù)的局部狀態(tài),因此當函數(shù)被再次調(diào)用時,它從上一次暫停的地方繼續(xù)執(zhí)行。

兩者區(qū)別:
  • 定義方式:迭代器是通過類來定義并實現(xiàn) __iter__()__next__() 方法;生成器是通過使用 yield 關鍵字的函數(shù)來定義的。不過生成器實際上是一種特殊的迭代器。
  • 使用方式:迭代器需要通過實現(xiàn)類來定義和管理數(shù)據(jù)迭代,而生成器通過函數(shù)的代碼執(zhí)行和 yield 表達式自動生成數(shù)據(jù)。
  • 內(nèi)存效率:生成器通常更節(jié)省內(nèi)存,因為它們會根據(jù)需要產(chǎn)生值,而不是一次性生成所有值。

應用

迭代器和生成器在 Python 中都有重要的作用,主要用于數(shù)據(jù)的迭代、處理和生成。它們在編寫可讀性和內(nèi)存效率更高的代碼方面特別有用。

大數(shù)據(jù)處理/惰性計算

當你處理大量數(shù)據(jù)時,使用生成器可以避免將整個數(shù)據(jù)集加載到內(nèi)存中,有些數(shù)據(jù)需要惰性計算(延遲計算),從而節(jié)省內(nèi)存。例如,逐行讀取大文件或者逐個處理大型數(shù)據(jù)集。

比如統(tǒng)計大文件有多少行:

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):
    """示例函數(shù),用于演示如何使用生成器處理大型文件。"""
    line_count = 0  # 行計數(shù)器
    for line in read_large_file(file_path):
        # 對每一行進行處理
        # 這里可以添加你自己的處理邏輯
        print(f"Processing line {line_count}: {line}")
        # 示例:簡單地統(tǒng)計行數(shù)
        line_count += 1
    print(f"Total lines processed: {line_count}")

if __name__ == "__main__":
    # 在這里指定要讀取的文件路徑
    file_path = "large_file.txt"
    # 使用process_large_file函數(shù)處理大文件
    process_large_file(file_path)
流式數(shù)據(jù)處理:

生成器適用于流式數(shù)據(jù)處理,數(shù)據(jù)流源源不斷地產(chǎn)生,你可以通過生成器逐一處理它們。例如,讀取數(shù)據(jù)流(如網(wǎng)絡流或數(shù)據(jù)庫流)并逐一處理。

比如下面這段源源不斷從redis隊列拉取數(shù)據(jù):

import time
import redis

def redis_stream_generator(redis_client, queue_name):
    """生成器函數(shù),用于從 Redis 隊列中流式獲取數(shù)據(jù)。"""
    while True:
        # 嘗試從 Redis 隊列中彈出一個數(shù)據(jù)項
        data = redis_client.blpop(queue_name, timeout=2)
        if data:
            # `data` 是一個元組 (queue_name, data)
            _, value = data
            # 使用 yield 返回數(shù)據(jù)
            yield value
        else:
            # 如果隊列為空,睡眠2秒
            time.sleep(2)

def process_redis_stream(data_stream):
    """處理 Redis 數(shù)據(jù)流的示例函數(shù)。"""
    for data in data_stream:
        # 對數(shù)據(jù)進行處理
        print(f"Processing data: {data}")

        # 在這里添加你的數(shù)據(jù)處理邏輯
        # 例如:對數(shù)據(jù)進行統(tǒng)計、計算等
        
        # 如果要在特定條件下停止處理,可以在這里添加邏輯

if __name__ == "__main__":
    # 連接到 Redis 服務器
    redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
    # 定義 Redis 隊列的名稱
    queue_name = 'my_queue'
    # 創(chuàng)建 Redis 數(shù)據(jù)流生成器
    data_stream = redis_stream_generator(redis_client, queue_name)
    # 處理 Redis 數(shù)據(jù)流
    process_redis_stream(data_stream)
無限序列:

生成器可以用來生成無限序列,例如斐波那契數(shù)列、素數(shù)序列等。這些序列往往無法通過列表來表示,因為它們是無限的,但可以通過生成器逐
一生成和處理。

def fibonacci_generator():
    """生成器函數(shù),用于生成斐波那契數(shù)列。"""
    a, b = 0, 1
    while True:
        # 使用 yield 返回當前的斐波那契數(shù)
        yield a
        # 計算下一個斐波那契數(shù)
        a, b = b, a + b


def process_fibonacci_sequence(n):
    """示例函數(shù),用于演示如何處理生成的斐波那契數(shù)列。"""
    # 創(chuàng)建 Fibonacci 數(shù)列的生成器
    fibonacci_gen = fibonacci_generator()
    print(f"First {n} numbers in the Fibonacci sequence:")
    # 使用生成器逐個獲取前 n 個斐波那契數(shù)
    for _ in range(n):
        fibonacci_number = next(fibonacci_gen)
        print(fibonacci_number)


if __name__ == "__main__":
    # 指定要生成的斐波那契數(shù)的個數(shù)
    n = 10  # 例如,獲取前 10 個斐波那契數(shù)
    # 處理并打印前 n 個斐波那契數(shù)
    process_fibonacci_sequence(n)
組合生成器:

通過生成器的組合,你可以創(chuàng)建復雜的數(shù)據(jù)流水線。例如,將多個生成器組合在一起,進行數(shù)據(jù)的提取、轉(zhuǎn)換和加載(ETL)操作。

協(xié)程和異步編程:

在 Python 的異步編程中,生成器和 async、await 等關鍵字結(jié)合使用,用于實現(xiàn)異步操作和協(xié)程。

比如下面這段異步批量采集某個api數(shù)據(jù)

import aiohttp
import asyncio

async def fetch_url(url):
    """
    異步函數(shù),用于發(fā)送網(wǎng)絡請求并返回響應的內(nèi)容。
    """
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            # 獲取響應內(nèi)容并返回
            content = await response.text()
            return content

async def main():
    """
    主函數(shù),演示如何并發(fā)發(fā)送多個網(wǎng)絡請求。
    """
    # 定義一組要請求的 URL 列表
    urls = [
        'http://xmishu.zhujinhui.net/api/hot_words',
        'http://xmishu.zhujinhui.net/api/hot_words',
        'http://xmishu.zhujinhui.net/api/hot_words',
    ]

    # 創(chuàng)建任務列表,使用列表推導式創(chuàng)建多個異步任務
    tasks = [fetch_url(url) for url in urls]

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

    # 輸出所有請求的結(jié)果
    for i, result in enumerate(results):
        print(f"Response from URL {urls[i]}:")
        print(result[:100])  # 只打印前100個字符

# 使用 asyncio.run 來運行主函數(shù)
if __name__ == '__main__':
    asyncio.run(main())

總結(jié)

迭代器是通過類來定義并實現(xiàn) __iter__()__next__() 方法,生成器是一個使用 yield 關鍵字來返回值的特殊函數(shù),也是一種特殊類型迭代器,它們在處理一些大數(shù)據(jù)問題的處理上都更能節(jié)省內(nèi)存。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容