Python 特有特性

本章學習知識點

  • 生成器(yield 關鍵字):節省內存的迭代器

  • 迭代器(iter/next):理解可迭代對象與迭代器的區別

  • 上下文管理器(with 語句):文件操作、數據庫連接(自動關閉資源)

  • 推導式:列表推導式、字典推導式、集合推導式(簡潔高效)

  • 優勢與適用場景

    特性 核心優勢 適用場景
    迭代器 統一遍歷邏輯、惰性計算 自定義遍歷邏輯、封裝複雜迭代規則
    生成器 極簡語法、內存極致優化 超大數據集處理、批量生成數據
    上下文管理器 資源自動管理、代碼安全簡潔 文件操作、數據庫連接、網絡連接等資源管理
    推導式 代碼簡潔、執行高效 快速創建列表/字典/集合、簡單數據過濾轉換

Python 憑藉其簡潔優雅且高效的語法特性,在眾多編程語言中獨樹一幟。其中,生成器、迭代器、上下文管理器和推導式是最具代表性的特有特性,它們不僅簡化了代碼編寫,更在內存優化、資源管理等核心場景中展現出強大優勢。

一、迭代器

在Python中,我們經常使用for循環遍歷列表、字典等數據結構,而支撐這一操作的底層機制就是「迭代器」。迭代器是一種可以逐個返回元素的對象,它實現了「迭代協議」,是高效遍歷數據的基礎。

1.1、核心概念

迭代器(Iterator)一定是可迭代對象(Iterable),但可迭代對象不一定是迭代器,二者是「包含與被包含」的關係,且核心目標都是支持「逐個取值」(遍歷),但設計定位和能力完全不同。

  • 核心定義

    概念 核心定義 核心功能
    可迭代對象 只要實現了 __iter__() 方法(或 __getitem__() 且索引從 0 開始),能被 for 循環遍歷的對象 提供「迭代的數據源」,是遍歷的「原材料」
    迭代器 同時實現 __iter__()__next__() 方法,且 __iter__() 返回自身的對象 執行「迭代的具體過程」,逐個產出數據,記錄遍歷位置
  • 關鍵區別

    維度 可迭代對象(Iterable) 迭代器(Iterator)
    核心方法 僅需實現 __iter__()(返回迭代器) 必須實現 __iter__()(返回自身)+ __next__()(返回下一個值,無值拋 StopIteration
    狀態性 無狀態(不記錄遍歷位置) 有狀態(記錄當前遍歷到的位置)
    一次性 可重複遍歷(每次遍歷生成新迭代器) 一次性遍歷(遍歷完後耗盡,無法重置,需重新生成)
    典型例子 列表 [1,2,3]、元組 (1,2,3)、字符串、字典、集合 列表迭代器(iter([1,2,3]))、生成器、enumerate() 結果、文件對象
    直接取值 不能直接用 next() 取值 可直接用 next() 逐個取值

1.2、迭代器使用

  • 核心功能及使用方法

    • 可迭代對象:「數據源」的角色

      可迭代對象是「能被遍歷的容器 / 數據源」,但它自己不會執行 “逐個取值” 的動作,而是通過 __iter__() 方法返回一個迭代器,讓迭代器去完成遍歷。

      # 列表是可迭代對象(Iterable)
      lst = [1, 2, 3]
      # 調用 __iter__() 得到迭代器(Iterator)
      it = iter(lst)  # 等價於 lst.__iter__()
      
    • 迭代器:「遍歷執行者」的角色

      迭代器是「真正幹活的」—— 通過 __next__() 方法逐個產出數據,且會記錄當前遍歷的位置(有狀態),遍歷完後再調用 next() 會拋出 StopIteration 異常(標誌遍歷結束)。

      # 1. 將可迭代對象(列表)轉為迭代器
      nums = [1, 2, 3, 4]
      num_iter = iter(nums)  # 轉換為迭代器
      
      # 2. 用 next() 逐個獲取元素
      print(next(num_iter))  # 輸出:1(獲取第一個元素)
      print(next(num_iter))  # 輸出:2(獲取第二個元素)
      print(next(num_iter))  # 輸出:3
      print(next(num_iter))  # 輸出:4
      # print(next(num_iter))  # 無元素時拋出 StopIteration 異常
      
    • for 循環的底層邏輯(串聯二者)

      for 循環,本質是自動完成了「可迭代對象→迭代器→逐個取值」的過程,自動處理 StopIteration

      for x in [1,2,3]:
          print(x)
          
      # 等價於
      it = iter([1,2,3])  # 1. 調用可迭代對象的 __iter__() 得到迭代器
      while True:
          try:
              x = next(it)  # 2. 調用迭代器的 __next__() 取值
              print(x)
          except StopIteration:  # 3. 捕獲異常,結束循環
              break
      
  • 自定義迭代器(實現迭代協議)

    • 通過在類中實現__iter__()__next__()方法,可自定義迭代器,實現複雜的遍歷邏輯。

      class MyIterator:
          def __init__(self, start, end):
              self.current = start  # 當前迭代值
              self.end = end        # 迭代終止值
      
          # 實現 __iter__():返回迭代器自身
          def __iter__(self):
              return self
      
          # 實現 __next__():返回下一個元素,無元素時拋異常
          def __next__(self):
              if self.current > self.end:
                  raise StopIteration  # 終止迭代的信號
              temp = self.current
              self.current += 1
              return temp
      
      # 使用自定義迭代器
      my_iter = MyIterator(1, 5)
      for num in my_iter:
          print(num, end=" ")  # 輸出:1 2 3 4 5
      
      # 迭代器是一次性的,再次遍歷無結果
      for num in my_iter:
          print(num, end=" ")  # 無輸出
      

1.3、迭代器總結

  • 核心價值

    1. 節省內存:迭代器是「惰性取值」(用的時候才生成值),比如生成器(一種迭代器)遍歷百萬級數據時,不會像列表一樣一次性加載所有數據到內存,而是取一個生成一個;

      # 生成器(迭代器):惰性取值,內存佔用極低
      gen = (x for x in range(1000000))
      print(next(gen))  # 0(僅生成第一個值)
      
    2. 統一遍歷接口:不管是列表、字典、文件等不同類型的可迭代對象,都能通過「轉迭代器→調 next」的方式遍歷,實現了遍歷邏輯的統一;

    3. 狀態保持:迭代器的 “狀態性” 讓遍歷過程能持續推進,而可迭代對象的 “無狀態性” 讓它可以重複被遍歷(每次生成新迭代器)。

    4. 迭代器的價值彙總: 在於「節省內存」(無需一次性加載所有數據)和「統一遍歷接口」(所有迭代器都能用 next()/for 遍歷)。

  • 總結

    • 可迭代對象:是 “數據源”,回答「遍歷什麼」的問題,無狀態、可重複遍歷,通過 __iter__() 交出迭代器;
    • 迭代器:是 “遍歷器”,回答「怎麼遍歷」的問題,有狀態、一次性遍歷,通過 __next__() 逐個產出數據。
    • 簡單記: 可迭代對象是 “倉庫”,迭代器是 “倉庫管理員”—— 倉庫提供數據,管理員負責逐個拿出數據,且管理員拿完一次就沒法回頭,要重新拿得換個新管理員(重新生成迭代器)。

二、生成器

生成器是 Python 中迭代器的一種特殊實現,核心特點是「惰性求值」(按需生成數據),既能大幅節省內存,又能簡化迭代邏輯。它是處理大規模數據、無限序列、分步計算的最優方案之一。

  • 生成器的核心定位

    生成器本質是:自動實現了 __iter__()__next__() 方法的迭代器,無需手動編寫這兩個方法,只需在函數中使用yield關鍵字即可,且遵循迭代器的「一次性、有狀態、惰性取值」規則。

    • 與普通迭代器的區別:生成器由 Python 解釋器自動生成迭代器協議的實現,代碼極簡;
    • 與列表 / 元組的區別:列表一次性加載所有數據到內存,生成器僅在調用 next() 時生成下一個值,內存佔用幾乎可忽略。

2.1、生成器的兩種創建方式

  • 生成器表達式(簡易版)

    • 語法和列表推導式幾乎一致,只是把 [] 換成 (),適合簡單的迭代邏輯。

    • 語法: gen = (表達式 for 變量 in 可迭代對象 if 條件)

      # 列表推導式(一次性生成所有數據,佔內存)
      lst = [x*2 for x in range(5)]
      print(lst)  # [0, 2, 4, 6, 8]
      
      # 生成器表達式(惰性求值,僅創建生成器對象,未生成任何值)
      gen = (x*2 for x in range(5))
      print(gen)  # <generator object <genexpr> at 0x10xxxxx>
      
      # 調用 next() 才生成值(有狀態,逐個產出)
      print(next(gen))  # 0
      print(next(gen))  # 2
      print(next(gen))  # 4
      
  • 生成器函數(yield 關鍵字)

    • 在函數中使用 yield 關鍵字(替代 return),函數調用後不會執行函數體,而是返回一個生成器對象;每次調用 next() 時,函數執行到 yield 處暫停並返回值,下次調用從暫停處繼續執行。

    • 核心規則:

      • yield:暫停函數執行,返回當前值,保留函數的變量狀態;
      • next()/send():喚醒函數,繼續執行到下一個 yield
      • 函數執行完(無更多 yield),調用 next() 拋出 StopIteration
    • 示例

      # 定義生成器函數(含 yield)
      def my_generator():
          print("開始執行...")
          yield 1  # 第一次 next() 執行到這裏,返回1,暫停
          print("繼續執行...")
          yield 2  # 第二次 next() 從這裏繼續,返回2,暫停
          print("執行結束...")  # 第三次 next() 執行完,無 yield,拋異常
      
      # 調用函數 → 不執行函數體,返回生成器對象
      gen = my_generator()
      print(gen)  # <generator object my_generator at 0x10xxxxx>
      
      # 逐個取值(觸發函數執行)
      print(next(gen))  # 打印「開始執行...」,返回 1
      print(next(gen))  # 打印「繼續執行...」,返回 2
      print(next(gen))  # 打印「執行結束...」,拋出 StopIteration
      
      #############################################################
      # 定義生成器函數(生成1~n的奇數)
      def odd_generator(n):
          current = 1
          while current <= n:
              yield current  # 返回current,並暫停函數
              current += 2
      
      # 調用生成器函數:返回生成器對象(不執行函數體)
      odd_gen = odd_generator(10)
      
      # 遍歷生成器(惰性計算)
      for num in odd_gen:
          print(num, end=" ")  # 輸出:1 3 5 7 9
      
      # 生成器是一次性的,再次遍歷無結果
      for num in odd_gen:
          print(num, end=" ")  # 無輸出
      
      # 用 next() 手動獲取元素
      odd_gen2 = odd_generator(5)
      print(next(odd_gen2))  # 輸出:1(執行到yield暫停)
      print(next(odd_gen2))  # 輸出:3(從暫停處繼續執行)
      print(next(odd_gen2))  # 輸出:5
      # print(next(odd_gen2))  # 拋 StopIteration 異常
      

2.2、生成器特性

  • 惰性求值

    • 生成器不會提前生成所有數據,只有調用 next()for 循環、list() 等觸發取值時,才會執行到 yield 並生成值;

    • 優勢:處理百萬 / 千萬級數據、無限序列時,內存佔用始終極低(僅保存當前狀態)。

    • 示例:無限序列生成器

      def infinite_num():
          n = 0
          while True:
              yield n
              n += 1
      
      gen = infinite_num()
      print(next(gen))  # 0
      print(next(gen))  # 1
      print(next(gen))  # 2
      # 永遠不會耗盡,按需取值,不佔內存
      
  • 有狀態且一次性

    • 生成器會記錄當前執行的位置(狀態),每次取值從暫停處繼續,無法重置;

    • 遍歷完成後(無更多 yield),再次調用 next() 會拋出 StopIteration,無法再次取值(需重新創建生成器)。

    • 示例: 一次性特性

      gen = (x for x in range(2))
      print(list(gen))  # [0, 1](觸發全部取值)
      print(list(gen))  # [](已耗盡,無值可取)拋出異常 StopIteration
      
  • 可暫停 / 恢復(yield 的特性)

    • yield 是生成器的 “暫停鍵”,next() 是 “恢復鍵”,這讓生成器可以實現「分步執行」,比如:

      • 分步處理複雜計算;
      • 實現協程(簡單版)。
    • 示例:分步計算

      def step_calc():
          x = yield 1          # 第一次返回1,暫停
          y = yield x + 2      # 第二次接收send的值,計算後返回,暫停
          yield y * 3          # 第三次返回最終結果
      
      gen = step_calc()
      print(next(gen))        # 1(第一步:返回初始值,暫停)
      print(gen.send(3))      # 5(第二步:x=3,返回3+2=5,暫停)
      print(gen.send(5))      # 15(第三步:y=5,返回5*3=15)
      
      • 參數説明

        調用方式 作用
        next(gen) 喚醒生成器,執行到下一個 yield不給暫停處的變量傳值(賦值 None)
        gen.send(val) 喚醒生成器,將 val 傳給暫停處的變量,再執行到下一個 yield
        第一次調用 必須用 next(gen)gen.send(None)(否則報錯,因為還未暫停在 yield 處)
  • 支持 send () 傳值(雙向通信)

    • 生成器不僅能返回值,還能通過 send(value) 向暫停的 yield 位置傳入值(替代 next()),實現「雙向通信」:

      • gen.send(None) 等價於 next(gen)(第一次必須傳 None,因為還未暫停在 yield 處);
      • gen.send(value):喚醒生成器,將 value 傳給當前 yield 左側的變量,然後執行到下一個 yield
      def chat():
          while True:
              msg = yield "請輸入消息:"  # 接收外部傳入的消息
              if msg == "exit":
                  yield "結束對話"
                  break
              yield f"你輸入了:{msg}"
      
      gen = chat()
      print(next(gen))  # 請輸入消息:(初始喚醒)
      print(gen.send("hello"))  # 你輸入了:hello
      print(next(gen))  # 請輸入消息:(繼續喚醒)
      # 如果輸入 print(gen.send("hello"))  也是喚醒操作
      print(gen.send("exit"))  # 結束對話
      

2.3、生成器的常用場景

  • 處理大規模數據:替代列表推導式,避免一次性加載所有數據到內存。

    # 處理1000萬條數據,生成器僅佔少量內存
    gen = (x for x in range(10000000) if x % 2 == 0)
    for num in gen:
        if num > 100:
            break
        print(num)
    
  • 讀取大文件(逐行讀取)

    Python 文件對象本身是迭代器,但生成器可封裝更復雜的讀取邏輯(比如按行處理、過濾)。

    def read_big_file(file_path):
        with open(file_path, "r") as f:
            for line in f:  # 文件對象逐行迭代,不加載全部內容
                yield line.strip()  # 處理後返回
    
    gen = read_big_file("big_file.txt")
    for line in gen:
        print(line)
    
  • 無限序列生成

    比如生成無限整數、斐波那契數列等,按需取值,永不耗盡。

    def fib():
        a, b = 0, 1
        while True:
            yield a
            a, b = b, a + b
    
    gen = fib()
    for _ in range(5):
        print(next(gen))  # 0, 1, 1, 2, 3
    
  • 實戰:生成器處理超大文件

    場景:處理10GB的日誌文件,提取包含"ERROR"的行。若用列表讀取,會一次性加載所有內容到內存導致內存溢出;用生成器可逐行讀取,內存佔用恆定。

    def error_log_generator(log_file):
        """生成器:逐行讀取日誌文件,返回包含ERROR的行"""
        with open(log_file, "r", encoding="utf-8") as f:
            for line in f:  # 文件對象本身就是迭代器,逐行讀取
                if "ERROR" in line:
                    yield line.strip()  # 返回錯誤行並暫停
    
    # 使用生成器處理超大日誌文件
    error_gen = error_log_generator("large_log.log")
    
    # 遍歷生成器,處理錯誤行(內存佔用始終很低)
    for error_line in error_gen:
        # 處理邏輯:如寫入錯誤日誌文件
        with open("error.log", "a", encoding="utf-8") as err_f:
            err_f.write(error_line + "\n")
    

2.4、生成器的高級用法

  • 生成器的關閉(close ())

    手動關閉生成器,後續調用 next() 會直接拋出 StopIteration,且會執行 finally 塊(如果有)。

    def gen_demo():
        try:
            yield 1
            yield 2
        finally:
            print("生成器被關閉")
    
    gen = gen_demo()
    print(next(gen))  # 1
    gen.close()  # 手動關閉
    print(next(gen))  # 拋出 StopIteration,且打印「生成器被關閉」
    
  • 生成器推導式 vs 列表推導式

    特性 生成器推導式 列表推導式
    內存佔用 極低(惰性求值) 高(一次性加載所有數據)
    執行速度 首次取值慢(按需生成) 初始化快(一次性生成)
    可迭代性 一次性遍歷 可重複遍歷
    適用場景 大規模數據、無限序列、分步處理 小規模數據、需重複使用的場景
  • 總結

    • 核心價值:
      • 極簡代碼:無需手動實現迭代器協議,yield 一鍵生成迭代器;
      • 節省內存:惰性求值,僅在需要時生成數據,適合大規模 / 無限數據;
      • 靈活控制:支持暫停 / 恢復、雙向通信,可實現分步執行、簡單協程。
    • 使用原則
      • 簡單迭代邏輯 → 生成器表達式;
      • 複雜 / 分步邏輯 → 生成器函數;
      • 需重複遍歷 / 小規模數據 → 列表;
      • 大規模 / 無限數據 → 生成器。

三、上下文管理器(with)

在Python中,操作文件、數據庫連接、網絡連接等資源時,必須確保使用後「正確關閉」(如文件不關閉可能導致數據丟失)。傳統方式用try-finally手動關閉資源,代碼繁瑣;而「上下文管理器」通過 with 語句簡化資源的「獲取 - 使用 - 釋放」流程(比如文件操作、數據庫連接、鎖管理等),避免因異常 / 忘記關閉導致的資源泄露。

  • 解決什麼問題?

    • with 管理文件

      # 手動管理資源,易出錯
      f = open("test.txt", "w")
      try:
          f.write("hello")
      except Exception as e:
          print("寫入失敗:", e)
      finally:
          f.close()  # 必須手動關閉,否則文件句柄泄露
      
    • with

      with open("test.txt", "w") as f:
          f.write("hello")
      # 無需手動close!with會自動關閉文件(無論是否拋異常)
      
    • 核心價值:

      • 自動釋放資源:無需手動調用 close()/release() 等方法;
      • 異常安全:即使代碼塊內拋出異常,資源仍會被正確釋放;
      • 代碼簡化:省去 try/finally 模板代碼,邏輯更清晰。

3.1、用法與原理

  • 基礎用法

    • 文件操作

      # 讀文件
      with open("test.txt", "r", encoding="utf-8") as f:
          content = f.read()
          print(content)
      # 退出with塊,文件自動關閉(即使read()拋異常)
      
      # 同時管理多個文件
      with open("in.txt", "r") as f1, open("out.txt", "w") as f2:
          f2.write(f1.read())  # 讀取f1,寫入f2
      
    • 鎖管理

      import threading
      lock = threading.Lock()
      
      # 自動獲取鎖,執行完釋放鎖
      with lock:
          print("臨界區代碼(線程安全)")
      
    • 數據庫連接

      import sqlite3
      
      # 自動提交/回滾事務,關閉連接
      with sqlite3.connect("test.db") as conn:
          cursor = conn.cursor()
          cursor.execute("CREATE TABLE IF NOT EXISTS user (id INT)")
          conn.commit()
      
  • 原理:上下文管理器協議

    • 上下文管理器協議

      with 語句的底層是「上下文管理器協議」,任何實現以下兩個方法的對象,都可以作為上下文管理器:

      方法 作用
      __enter__() 進入 with 塊時執行:<br />1. 初始化資源(如打開文件、獲取鎖);<br />2. 返回值會被賦值給 as 後的變量
      __exit__(exc_type, exc_val, exc_tb) 退出 with 塊時執行:<br />1. 釋放資源(如關閉文件、釋放鎖);<br />2. 處理異常(可選)
    • 執行流程拆解

      with open("test.txt","r", encoding='utf-8') as f:
          f.read()
      
      #### 等價於 ##################
      cm = open("test.txt")						 # 1. 創建上下文管理器對象
      try:
          f = cm.__enter__()					     # 2. 執行__enter__,返回值賦值給f
          f.read()								 # 3. 執行with塊內的代碼
      except Exception as e:
          # (type(e), e, e.__traceback__)          <-- 這裏是固定的值
          cm.__exit__(type(e), e, e.__traceback__) # 4. 執行__exit__,傳入異常信息
          raise  									 # 默認重新拋出異常
      else:
          cm.__exit__(None, None, None)			 # 無異常時也執行__exit__
      

3.2、自定義上下文管理器(兩種方式)

  • 類實現(最靈活,支持複雜邏輯)

    實現 __enter____exit__ 方法即可,示例:自定義文件管理器(模擬 open

    class MyFileManager:
        def __init__(self, file_path, mode="r"):
            self.file_path = file_path
            self.mode = mode
            self.file = None
    
        # 進入with塊:打開文件,返回文件對象
        def __enter__(self):
            print("打開文件...")
            self.file = open(self.file_path, self.mode)
            return self.file  # 賦值給as後的變量
    
        # 退出with塊:關閉文件,處理異常
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("關閉文件...")
            if self.file:
                self.file.close()
    
            # 可選:處理異常(返回True則抑制異常,False則拋出)
            if exc_type is not None:
                print(f"捕獲異常:{exc_type}, {exc_val}")
                # return True  # 註釋掉則拋出異常,打開則抑制
            return False
    
    # 使用自定義上下文管理器
    with MyFileManager("test.txt", "w") as f:
        f.write("自定義管理器")
        # 故意拋異常測試
        # 1 / 0  # 取消註釋,會觸發__exit__的異常處理
    
  • 裝飾器實現(極簡,適合簡單邏輯)

    • contextlib.contextmanager 裝飾器,將生成器函數轉為上下文管理器,無需寫類。核心規則:

      • 生成器中 yield 之前的代碼 → 對應 __enter__()

      • yield 之後的代碼 → 對應 __exit__()

      • yield的值 → 賦值給as` 後的變量。

    • 示例:簡化版文件管理器

      from contextlib import contextmanager
      
      @contextmanager
      def my_file_manager(file_path, mode="r"):
          # __enter__ 邏輯:打開文件
          print("打開文件...")
          file = open(file_path, mode)
          try:
              yield file  # 返回文件對象,暫停執行
          finally:
              # __exit__ 邏輯:關閉文件(無論是否拋異常)
              print("關閉文件...")
              file.close()
      
      # 使用
      with my_file_manager("test.txt", "w") as f:
          f.write("裝飾器實現")
      
  • 總結

    • 實現方式

      場景 推薦實現方式
      簡單邏輯(一行搞定) contextlib.contextmanager 裝飾器
      複雜邏輯(帶狀態 / 異常處理) 類實現 __enter__/__exit__
      內置資源(文件 / 鎖) 直接用 with + 內置上下文管理器
    • 核心點

      • __enter__ 負責「獲取資源」,__exit__ 負責「釋放資源」;
      • with 語句本質是 try/finally 的語法糖,但更簡潔、更安全。
    • 日常開發中,優先用 with 管理所有「需要顯式關閉的資源」(文件、連接、鎖等),避免資源泄露。

四、推導式

推導式是Python中創建列表、字典、集合的「極簡語法」,它將for循環、條件判斷等邏輯濃縮為一行代碼,不僅簡潔易讀,且執行效率比普通for循環更高(底層優化)。

  • 數據結構中的生成式跟推導式有啥區別? 沒區別,原來只是口語不一樣

    推導式類型 語法格式 別稱(生成式) 產出對象 核心特點
    列表推導式 [expr for x in iter if cond] 列表生成式 列表(list) 一次性加載所有數據到內存
    字典推導式 {k:v for x in iter if cond} 字典生成式 字典(dict) 鍵唯一,值可重複
    集合推導式 {expr for x in iter if cond} 集合生成式 集合(set) 元素去重,無序
    生成器表達式 (expr for x in iter if cond) 生成器生成式(口語) 生成器(generator) 惰性求值,僅佔少量內存

    發現沒有元組推導式,因為()包裹的是生成器表達式,如果硬要就是 tuple(expr for x in iter)

  • 為什麼會有 “生成式” 這個叫法?

    “生成式” 是中文使用者的口語化簡化稱呼

    • 推導式的核心動作是 “推導 / 計算”(Comprehension),強調 “從可迭代對象推導出新容器”;
    • 生成式的核心動作是 “生成”,強調 “快速生成一個容器 / 生成器對象”;
    • 本質是同一語法的兩種描述角度,比如:
      • 列表推導式:從邏輯上 “推導” 出列表(規範);
      • 列表生成式:從結果上 “生成” 了列表(口語)。

4.1、列表推導式

  • 語法格式:

    [表達式 for 變量 in 可迭代對象 if 條件判斷]
    
    • 表達式:最終添加到列表的元素(可對變量進行運算、轉換等)。
    • if 條件判斷(可選):僅滿足條件的變量才會執行表達式並添加到列表。
  • 示例

    # 案例1:基礎用法(創建1~10的平方列表)
    square_list = [x**2 for x in range(1, 11)]
    print(square_list)  # 輸出:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    
    # 案例2:帶條件判斷(創建1~20的偶數列表)
    even_list = [x for x in range(1, 21) if x % 2 == 0]
    print(even_list)  # 輸出:[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
    
    # 案例3:複雜表達式(字符串處理:將列表元素轉為大寫並去重)
    fruits = ["apple", "banana", "apple", "cherry", "banana"]
    upper_fruits = [fruit.upper() for fruit in fruits if len(fruit) > 5]
    print(upper_fruits)  # 輸出:['BANANA', 'BANANA', 'CHERRY']
    
    # 案例4:嵌套推導式(二維列表轉一維列表)
    matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    flatten_list = [x for row in matrix for x in row]
    print(flatten_list)  # 輸出:[1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    # 對比:普通for循環(代碼繁瑣,效率低)
    flatten_list2 = []
    for row in matrix:
        for x in row:
            flatten_list2.append(x)
    

4.2、字典推導式

  • 語法格式

    {鍵表達式: 值表達式 for 變量 in 可迭代對象 if 條件判斷}
    

    核心是生成鍵值對,適合從可迭代對象快速構建字典,或對現有字典進行轉換。

  • 示例

    # 案例1:基礎用法(創建1~5的平方字典)
    square_dict = {x: x**2 for x in range(1, 6)}
    print(square_dict)  # 輸出:{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
    
    # 案例2:帶條件判斷(篩選鍵為偶數的鍵值對)
    num_dict = {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"}
    even_key_dict = {k: v for k, v in num_dict.items() if k % 2 == 0}
    print(even_key_dict)  # 輸出:{2: 'b', 4: 'd'}
    
    # 案例3:兩個列表映射為字典
    names = ["小明", "小紅", "小李"]
    ages = [20, 18, 19]
    name_age_dict = {name: age for name, age in zip(names, ages)}
    print(name_age_dict)  # 輸出:{'小明': 20, '小紅': 18, '小李': 19}
    
    # 案例4:字典值轉換(將值轉為大寫)
    fruit_dict = {"a": "apple", "b": "banana", "c": "cherry"}
    upper_fruit_dict = {k: v.upper() for k, v in fruit_dict.items()}
    print(upper_fruit_dict)  # 輸出:{'a': 'APPLE', 'b': 'BANANA', 'c': 'CHERRY'}
    

4.3、集合推導式

  • 語法格式

    {表達式 for 變量 in 可迭代對象 if 條件判斷}
    

    與列表推導式類似,區別是用()改為{},且生成的集合會自動去重(集合的核心特性)。

  • 示例

    # 案例1:基礎用法(創建1~10的偶數集合)
    even_set = {x for x in range(1, 11) if x % 2 == 0}
    print(even_set)  # 輸出:{2, 4, 6, 8, 10}
    
    # 案例2:自動去重(從重複列表生成無重集合)
    duplicate_nums = [1, 2, 2, 3, 3, 3, 4, 5, 5]
    unique_set = {x for x in duplicate_nums}
    print(unique_set)  # 輸出:{1, 2, 3, 4, 5}
    
    # 案例3:字符串處理(提取字符串中的唯一字符並轉為大寫)
    s = "abracadabra"
    unique_chars = {c.upper() for c in s}
    print(unique_chars)  # 輸出:{'A', 'B', 'C', 'D', 'R'}
    

4.4、注意事項

  • 避免過度複雜:推導式適合簡潔邏輯,若包含多層嵌套(如3層以上for循環),建議拆分為普通for循環,保證代碼可讀性。
  • 區分生成器表達式:生成器表達式是(表達式 for ...),返回生成器對象;集合推導式是{表達式 for ...},返回集合對象,不可混淆。
  • 效率優勢:推導式的執行效率比普通for循環高20%~50%(底層用C語言優化),但對於超大數據集,建議用生成器(避免內存溢出)。

4.5、實戰

數據處理流水線場景:處理一個1GB的用户行為日誌文件,需求如下:

  1. 逐行讀取日誌(內存優化);
  2. 解析日誌中的用户ID和行為類型;
  3. 統計各行為類型的用户數量(去重);
  4. 將統計結果保存為JSON文件(資源自動管理)。
import json
from collections import defaultdict
from contextlib import contextmanager

# 1. 生成器:逐行讀取日誌(內存優化)
def log_generator(log_file):
    """生成器:逐行讀取日誌,返回解析後的用户ID和行為類型"""
    with open(log_file, "r", encoding="utf-8") as f:
        for line in f:  # 文件對象是迭代器,逐行讀取
            line = line.strip()
            if not line:
                continue
            # 日誌格式:"2025-11-27 10:00:00 | user_123 | click"
            parts = line.split(" | ")
            if len(parts) == 3:
                user_id = parts[1]
                action = parts[2]
                yield (user_id, action)  # 返回解析結果,暫停

# 2. 上下文管理器:JSON文件保存(自動關閉文件)
@contextmanager
def json_writer(file_path):
    """上下文管理器:寫入JSON文件"""
    f = open(file_path, "w", encoding="utf-8")
    try:
        yield f
    finally:
        f.close()

# 3. 主邏輯:數據處理流水線
def analyze_user_action(log_file, output_file):
    # 用defaultdict存儲各行為的用户集合(自動去重)
    action_user = defaultdict(set)
    
    # 遍歷生成器,解析日誌並統計
    for user_id, action in log_generator(log_file):
        action_user[action].add(user_id)  # 集合自動去重
    
    # 整理統計結果:{行為類型: 用户數量}
    result = {action: len(users) for action, users in action_user.items()}
    
    # 用上下文管理器保存為JSON
    with json_writer(output_file) as f:
        json.dump(result, f, ensure_ascii=False, indent=2)
    
    print("統計完成!結果已保存至:", output_file)
    return result

# 4. 執行分析(模擬日誌文件)
# 先創建模擬日誌文件(1GB實際文件可直接替換路徑)
with open("user_action.log", "w", encoding="utf-8") as f:
    actions = ["click", "view", "purchase", "collect"]
    for i in range(1000000):  # 100萬條模擬日誌
        user_id = f"user_{i%10000}"  # 1萬個唯一用户
        action = actions[i%4]  # 循環行為類型
        f.write(f"2025-11-27 10:00:00 | {user_id} | {action}\n")

# 執行分析
result = analyze_user_action("user_action.log", "action_stat.json")
print("統計結果:", result)

五、常見易錯點與避坑指南

  1. 迭代器重複遍歷問題:迭代器是一次性的,遍歷結束後會耗盡,再次遍歷無結果。解決方案:若需重複遍歷,可將迭代器轉為列表(list(iter_obj)),但需注意內存佔用。
  2. 生成器暫停邏輯誤解:生成器函數中yield後的代碼會在下次調用時執行,而非直接跳過。例如:yield x; x +=1,下次調用會先執行x +=1再返回下一個值。
  3. 上下文管理器異常處理__exit__()方法返回True表示已處理異常,不向外拋出;返回False(默認)則會將異常向外傳遞。自定義時需謹慎處理。
  4. 推導式過度嵌套:如[x for row in matrix for col in row for x in col](三維轉一維),代碼可讀性差。解決方案:拆分為普通循環或使用itertools.chain等工具。
  5. 生成器表達式與元組混淆(x for x in range(5))是生成器表達式,返回生成器;tuple(x for x in range(5))才是元組。