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

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

概念

迭代器(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 關(guān)鍵字來返回值。

  • 當生成器函數(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 關(guān)鍵字的函數(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 等關(guān)鍵字結(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 關(guān)鍵字來返回值的特殊函數(shù)哲嘲,也是一種特殊類型迭代器,它們在處理一些大數(shù)據(jù)問題的處理上都更能節(jié)省內(nèi)存媳禁。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眠副,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子损话,更是在濱河造成了極大的恐慌侦啸,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丧枪,死亡現(xiàn)場離奇詭異光涂,居然都是意外死亡,警方通過查閱死者的電腦和手機拧烦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門忘闻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恋博,你說我怎么就攤上這事齐佳。” “怎么了债沮?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵炼吴,是天一觀的道長。 經(jīng)常有香客問我疫衩,道長硅蹦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任闷煤,我火速辦了婚禮童芹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鲤拿。我一直安慰自己假褪,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布近顷。 她就那樣靜靜地躺著生音,像睡著了一般宁否。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上久锥,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天家淤,我揣著相機與錄音异剥,去河邊找鬼瑟由。 笑死,一個胖子當著我的面吹牛冤寿,可吹牛的內(nèi)容都是我干的歹苦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼督怜,長吁一口氣:“原來是場噩夢啊……” “哼殴瘦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起号杠,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚪腋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后姨蟋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屉凯,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年眼溶,在試婚紗的時候發(fā)現(xiàn)自己被綠了悠砚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡堂飞,死狀恐怖灌旧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绰筛,我是刑警寧澤枢泰,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站铝噩,受9級特大地震影響衡蚂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜薄榛,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一讳窟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敞恋,春花似錦丽啡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽改执。三九已至,卻和暖如春坑雅,著一層夾襖步出監(jiān)牢的瞬間辈挂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工裹粤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留终蒂,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓遥诉,卻偏偏與公主長得像拇泣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子矮锈,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容