土薯工具 Toolshu.com 登錄 用戶注冊

Python 列表推導式比 for 循環快多少,什麼時候根本沒差?

作者:bhnw 於 2026-04-25 10:26 發佈 5次瀏覽 收藏 (0)

列表推導式"更快"——但快在哪裏,什麼時候快,什麼時候根本沒差?

網上隨便搜,都能找到"列表推導式比 for 循環快"這個說法。大部分文章給你跑個 timeit,展示列表推導式快了 2 倍,然後說用列表推導式吧,完事。

但實際寫代碼的時候經常會發現:換成列表推導式,壓根看不出快了多少。這不是錯覺,是有原因的。


先說結論,再講原因

  • 簡單操作(乘以2、加1、類型轉換這類):列表推導式比 for 循環快 20%-50%,有時接近 2 倍
  • 複雜操作(函數調用、正則匹配、IO、數據庫查詢這類):基本沒有差別,快不了幾個毫秒
  • 數據量很大、內存吃緊:列表推導式反而是問題,生成器表達式纔是正確選擇

爲什麼簡單操作時列表推導式更快?

dis 模塊看一下字節碼就清楚了:

import dis

# for 循環版本
def for_loop():
    result = []
    for i in range(10):
        result.append(i * 2)
    return result

# 列表推導式版本
def list_comp():
    return [i * 2 for i in range(10)]

dis.dis(for_loop)
dis.dis(list_comp)

for 循環的字節碼裏,每次迭代都要做:

  1. LOAD_FAST:找到 result 變量
  2. LOAD_ATTR:查找 .append 屬性——這步最貴
  3. CALL_FUNCTION:調用 append

列表推導式用的是專門的字節碼指令 LIST_APPEND,繞過了屬性查找,直接在 C 層面操作列表。循環10萬次,省了10萬次屬性查找,差距就出來了。

用 timeit 驗證一下:

import timeit

# 簡單操作:每個數乘以2
setup = "data = list(range(1_000_000))"

t_for = timeit.timeit(
    "[result.append(x * 2) for _ in [None] if False] or [x * 2 for x in data]",
    setup=setup, number=10
)

# 更直觀的對比
def for_loop(data):
    result = []
    for x in data:
        result.append(x * 2)
    return result

def list_comp(data):
    return [x * 2 for x in data]

data = list(range(1_000_000))

t1 = timeit.timeit(lambda: for_loop(data), number=50)
t2 = timeit.timeit(lambda: list_comp(data), number=50)

print(f"for loop:  {t1:.3f}s")
print(f"list comp: {t2:.3f}s")
print(f"快了: {t1/t2:.2f}x")
# 典型輸出:
# for loop:  2.341s
# list comp: 1.487s
# 快了: 1.57x

爲什麼複雜操作時沒有差別?

道理很簡單:當操作本身耗時遠超循環開銷,那點屬性查找的時間就是九牛一毛。

import math
import timeit

def heavy_for(data):
    result = []
    for x in data:
        result.append(math.sqrt(x) ** 2.5 + math.log(x + 1))
    return result

def heavy_comp(data):
    return [math.sqrt(x) ** 2.5 + math.log(x + 1) for x in data]

data = list(range(1, 100_001))

t1 = timeit.timeit(lambda: heavy_for(data), number=20)
t2 = timeit.timeit(lambda: heavy_comp(data), number=20)

print(f"for loop:  {t1:.3f}s")
print(f"list comp: {t2:.3f}s")
# 差距往往在 5% 以內,基本可以忽略

如果循環體裏有網絡請求、數據庫查詢、文件讀寫,那 for 循環和列表推導式的性能差距就更加可以忽略——網絡延遲動輒幾十毫秒,append 的開銷根本不值一提。


內存:列表推導式的隱患

列表推導式是"餓漢",執行的瞬間就把所有結果放進內存。

# 這行代碼會立刻佔用幾百 MB 內存
big_list = [x * 2 for x in range(10_000_000)]

# 生成器表達式是"懶漢",幾乎不佔內存
big_gen = (x * 2 for x in range(10_000_000))

import sys
print(sys.getsizeof(big_list))  # ~85 MB
print(sys.getsizeof(big_gen))   # 200 Bytes 左右

如果你只是要遍歷結果一次(比如求和、找最大值、傳給另一個函數),根本不需要列表推導式,直接用生成器表達式:

# 不需要建完整列表
total = sum(x * 2 for x in range(10_000_000))      # 內存友好
maximum = max(x ** 2 for x in range(10_000_000))   # 內存友好

# 而不是
total = sum([x * 2 for x in range(10_000_000)])    # 先建完整列表再求和,浪費

什麼時候不該用列表推導式

邏輯複雜的時候。 列表推導式超過兩層條件或者嵌套兩層以上,可讀性會急劇下降,這時候老老實實寫 for 循環反而更好:

# 這已經很難看懂了
result = [x for sublist in matrix for x in sublist if x > 0 if x % 2 == 0]

# 換成 for 循環,起碼能加註釋
result = []
for sublist in matrix:
    for x in sublist:
        if x > 0 and x % 2 == 0:
            result.append(x)

需要中途 break 的時候。 列表推導式沒法 break,找到第一個滿足條件的元素就停下來,只能用 for 循環或者 next()

# 找第一個大於100的數,列表推導式做不到高效
data = range(1_000_000)

# 錯誤的做法:把100萬個元素全遍歷了
first = [x for x in data if x > 100][0]

# 正確的做法
first = next(x for x in data if x > 100)
# 或者
for x in data:
    if x > 100:
        first = x
        break

需要處理異常的時候。 列表推導式裏沒辦法 try-except:

# 有些字符串可能轉換失敗,只能用 for 循環
def safe_int(s):
    try:
        return int(s)
    except ValueError:
        return None

data = ["1", "abc", "3", "xyz"]
result = [safe_int(x) for x in data]  # 這樣可以,把異常處理封進函數
# 但不能在推導式內部直接寫 try-except

一張表總結

場景 推薦寫法
簡單變換,結果要放進列表 列表推導式
只需遍歷一次結果(求和/最大值等) 生成器表達式
數據量超大,內存有限 生成器表達式
操作複雜(函數調用、IO) 隨意,可讀性優先
邏輯複雜,需要註釋 for 循環
需要 break 或 continue for 循環
需要 try-except for 循環,或把異常處理封進函數

想直接跑這些代碼驗證結果,粘到 Python 在線運行工具 裏就行,Python 3.12 環境,timeit 和 dis 模塊都支持。

发现周边 发现周边
評論區

加載中...