Python 中可變對象的“引用賦值”特性——可變對象的“引用傳遞”
一、踩坑代碼
某程序老鳥講了一個故事:
“2019年夏天,我在做一個推薦系統的用戶畫像模塊。當時寫了這樣的代碼:
# 當時的蠢代碼,現在想起來都臉紅
default_preferences = [] # 想著所有用戶共享一個默認偏好
users = {}
for user_id in user_ids:
users[user_id] = default_preferences # 媽的,大坑!
所有用戶共享了同一個列表!
用戶A喜歡看動作片,結果用戶B、C、D全都變成動作片愛好者了。線上故障持續了2個小時,被領導罵得狗血淋頭。
那天晚上我查了一夜的資料,才徹底搞懂Python的對象模型。”
解釋一下:
# 1. 創建一個列表(內存地址假設為 0x123)
default_preferences = []
# 2. 循環給 3 個用戶賦值
user_ids = [1,2,3]
users = {}
for user_id in user_ids:
users[user_id] = default_preferences # 所有用戶都指向 0x123 的列表
# 3. 給用戶1添加“動作片”偏好
users[1].append("動作片")
# 4. 查看所有用戶的偏好——全變成了動作片!
print(users[1]) # ['動作片'](改的是 0x123 的列表)
print(users[2]) # ['動作片'](指向同一個 0x123)
print(users[3]) # ['動作片'](同樣指向 0x123)
本質是:以為給每個用戶“分配了一個新列表”,實際是“讓所有用戶共用同一個列表”——這就是線上故障的根源:用戶A修改偏好,等于修改了所有人的偏好。
二、怎么改才對?(避免共享可變對象)
核心思路:給每個用戶創建“獨立的新列表”,而不是復用同一個列表的引用。有兩種常見寫法:
1. 循環內每次創建新列表(推薦)
在循環里直接定義空列表,每個用戶拿到的都是內存中不同的新列表:
user_ids = [1,2,3]
users = {}
for user_id in user_ids:
# 每次循環都新建一個空列表(內存地址不同)
users[user_id] = []
# 給用戶1加偏好,只影響用戶1
users[1].append("動作片")
print(users[1]) # ['動作片']
print(users[2]) # [](獨立列表,不受影響)
2. 用列表的 copy() 方法(適合有默認值的場景)
如果默認偏好不是空列表(比如有默認值 ["喜劇片"]),可以用 copy() 復制一個新列表給每個用戶:
# 有默認值的列表
default_preferences = ["喜劇片"]
user_ids = [1,2,3]
users = {}
for user_id in user_ids:
# 復制一個新列表(內存地址不同),避免共享
users[user_id] = default_preferences.copy()
# 給用戶1加動作片,不影響其他人
users[1].append("動作片")
print(users[1]) # ['喜劇片', '動作片']
print(users[2]) # ['喜劇片'](默認值,未被修改)
三、為什么這個“坑”很容易踩?(新手常見誤區)
很多人會誤以為“賦值就是復制數據”,但忽略了“可變對象 vs 不可變對象”的區別:
- 比如給整數賦值
a = 1; b = a; b = 2,a還是 1(不可變對象,賦值是副本); - 但給列表賦值
a = []; b = a; b.append(1),a也會變成[1](可變對象,賦值是引用)。
你的場景里,“想讓所有用戶有默認偏好”是合理需求,但錯把“共享引用”當成了“共享默認值”——最終導致數據串用,線上故障。
總結
這段代碼的“蠢”不是邏輯錯,而是對 Python 可變對象的賦值機制理解不到位:
- 可變對象(列表、字典等)賦值傳遞“引用”,不是“副本”;
- 循環中給多個變量賦值同一個可變對象,會導致所有變量共享數據;
- 解決辦法:給每個用戶創建獨立的可變對象(循環內新建,或用
copy()復制)。
這種坑很典型,很多新手(甚至工作幾年的開發者)都踩過——吃一次線上故障的虧,對“引用傳遞”的理解就再也忘不掉了~

浙公網安備 33010602011771號