Python中可變對象與不可變對象詳細(xì)教程
前言
我們來深入解析Python中一個核心概念:可變(Mutable)對象和不可變(Immutable)對象。這個概念直接關(guān)系到你的代碼在內(nèi)存中如何運(yùn)作,尤其是在進(jìn)行賦值、拷貝、傳參等操作時,理解它可以幫助你避免許多難以發(fā)現(xiàn)的bug。
核心定義
判斷一個對象是可變的還是不可變的,標(biāo)準(zhǔn)只有一個:當(dāng)對象創(chuàng)建之后,它里面的值能否被改變?
不可變對象:對象創(chuàng)建后,其內(nèi)容(或狀態(tài))無法被修改。如果你嘗試修改它,Python實際上會創(chuàng)建一個新的對象,而不是在原有對象上修改。
可變對象:對象創(chuàng)建后,其內(nèi)容(或狀態(tài))可以被修改。你可以在原內(nèi)存地址上直接修改對象的值,而不會創(chuàng)建新對象。
1. 常見的不可變對象
整數(shù)(int)
浮點數(shù)(float)
布爾值(bool)
字符串(str)
元組(tuple) —— 注意:元組本身不可變,但如果它包含可變對象(如列表),那個內(nèi)部對象是可變的。
凍結(jié)集合(frozenset)
示例:看似"修改"了不可變對象
name = "hello"
print(f"原始對象的內(nèi)存地址: {id(name)}") # id() 可以查看對象的內(nèi)存地址
name = name + " world"
print(f"新對象的內(nèi)存地址: {id(name)}")
# 輸出:
# 原始對象的內(nèi)存地址: 1399876543210 (示例地址)
# 新對象的內(nèi)存地址: 1399876545678 (地址變了!)發(fā)生了什么?
當(dāng)執(zhí)行 name = name + " world" 時,Python并沒有在原來的字符串 "hello" 后面添加內(nèi)容。因為字符串是不可變的,它不能直接在原處修改。所以,Python在內(nèi)存中創(chuàng)建了一個全新的字符串對象 "hello world",然后將變量 name 重新指向了這個新對象。原來的 "hello" 對象如果沒有其他變量引用,最終會被垃圾回收。
2. 常見的可變對象
列表(list)
字典(dict)
集合(set)
自定義類的實例對象(默認(rèn)情況下)
示例:修改可變對象
my_list = [1, 2, 3]
print(f"修改前列表的內(nèi)存地址: {id(my_list)}")
my_list.append(4) # 在列表末尾添加一個元素
print(f"修改后列表的內(nèi)容: {my_list}")
print(f"修改后列表的內(nèi)存地址: {id(my_list)}")
# 輸出:
# 修改前列表的內(nèi)存地址: 1399876547890 (示例地址)
# 修改后列表的內(nèi)容: [1, 2, 3, 4]
# 修改后列表的內(nèi)存地址: 1399876547890 (地址沒變!)示例2:
# 創(chuàng)建一個可變對象(列表)
a = [1, 2, 3] # a 指向一個列表對象 [1, 2, 3]
print(f"初始狀態(tài):")
print(f"a = {a}, id(a) = {id(a)}")
b = a # b 指向同一個列表對象
print(f"\n執(zhí)行 b = a 后:")
print(f"a = {a}, id(a) = {id(a)}")
print(f"b = , id(b) = {id(b)}")
print(f"a is b? {a is b}") # True,確實是同一個對象
# 情況1:通過 a 修改列表內(nèi)容(不是重新賦值)
a.append(4) # 修改可變對象的內(nèi)容
print(f"\n執(zhí)行 a.append(4) 后:")
print(f"a = {a}, id(a) = {id(a)}")
print(f"b = , id(b) = {id(b)}")
print(f"b 看到了變化!因為 a 和 b 指向同一個列表")
# 情況2:重新給 a 賦值(讓 a 指向新對象)
a = [5, 6, 7] # a 現(xiàn)在指向一個新列表
print(f"\n執(zhí)行 a = [5, 6, 7] 后:")
print(f"a = {a}, id(a) = {id(a)}")
print(f"b = , id(b) = {id(b)}")
print(f"b 仍然指向原來的列表,不受 a 重新賦值的影響")輸出:
初始狀態(tài): a = [1, 2, 3], id(a) = 1399876543210 執(zhí)行 b = a 后: a = [1, 2, 3], id(a) = 1399876543210 b = [1, 2, 3], id(b) = 1399876543210 a is b? True 執(zhí)行 a.append(4) 后: a = [1, 2, 3, 4], id(a) = 1399876543210 b = [1, 2, 3, 4], id(b) = 1399876543210 b 看到了變化!因為 a 和 b 指向同一個列表 執(zhí)行 a = [5, 6, 7] 后: a = [5, 6, 7], id(a) = 1399876545678 b = [1, 2, 3, 4], id(b) = 1399876543210 b 仍然指向原來的列表,不受 a 重新賦值的影響
發(fā)生了什么?
當(dāng)我們調(diào)用 my_list.append(4) 時,Python直接在原有的列表對象上進(jìn)行了修改。它沒有創(chuàng)建新的列表,只是改變了原列表的內(nèi)部狀態(tài)。因此,列表的內(nèi)存地址在修改前后是完全相同的。
3. 為什么這個區(qū)別很重要?
理解可變與不可變,對于編寫正確的代碼至關(guān)重要,尤其是在以下場景:
a) 變量賦值和引用
內(nèi)存示意圖:
棧內(nèi)存:
a: [10] → a: [20] (a的值被直接修改)
b: [10] b: [10] (b獨(dú)立存儲,不受影響)
2. 對于引用類型(Reference Types)
// 假設(shè)有一個 Person 類
Person a = new Person("Alice"); // a 存儲的是對象的引用(內(nèi)存地址)
Person b = a; // 把引用(地址)復(fù)制給 b
a.setName("Bob"); // 通過 a 修改對象內(nèi)容
System.out.println(b.getName()); // 輸出: "Bob" (b 指向同一個對象)
a = new Person("Charlie"); // a 指向一個新對象
System.out.println(b.getName()); // 輸出: "Bob" (b 仍然指向原來的對象)b) 函數(shù)參數(shù)的傳遞
Python的參數(shù)傳遞方式是"對象引用傳遞"。這意味著函數(shù)內(nèi)部接收的是指向?qū)嶋H對象的引用。
c) 作為字典的鍵
4. 特殊情況:元組中包含可變對象
這是一個需要特別注意的地方。元組本身是不可變的,這意味著你不能給元組添加或刪除元素,也不能替換元組中的某個元素。但是,如果元組中包含了可變對象(如列表),那么這個可變對象的內(nèi)容是可以被修改的。
t = (1, 2, [3, 4]) # 元組中包含一個列表 print(t) # 輸出: (1, 2, [3, 4]) # t[0] = 10 # 錯誤!不能替換元組的元素,會報 TypeError # t.append(5) # 錯誤!元組沒有 append 方法 t[2][0] = 999 # 可以修改元組內(nèi)部列表的元素 print(t) # 輸出: (1, 2, [999, 4]) (元組的內(nèi)容看起來變了!)
解釋:
雖然元組存儲的引用本身不能變,但它并沒有限制這些引用所指向的對象內(nèi)部是否能變。這里,元組中第三個槽位始終指向同一個列表對象。我們只是修改了這個列表對象的內(nèi)容,并沒有改變元組中存儲的引用。所以從元組的角度看,它依然是"不可變"的。
總結(jié)
| 特性 | 不可變對象 | 可變對象 |
|---|---|---|
| 常見類型 | int, float, str, tuple, frozenset | list, dict, set, 自定義類實例 |
| 修改操作 | 任何修改都會創(chuàng)建一個新對象 | 可以在原內(nèi)存地址上直接修改內(nèi)容 |
| 內(nèi)存地址 | 修改后內(nèi)存地址會改變 | 修改后內(nèi)存地址通常不變 |
| 安全性 | 多個引用共享時安全,不會被意外修改 | 多個引用共享時需謹(jǐn)慎,一處修改處處可見 |
| 作為字典鍵 | 可以 | 不可以 |
| 函數(shù)傳參 | 函數(shù)內(nèi)修改不會影響外部變量 | 函數(shù)內(nèi)修改會影響外部對象 |
理解這個核心區(qū)別,是掌握Python內(nèi)存模型和寫出健壯代碼的關(guān)鍵一步。
到此這篇關(guān)于Python中可變對象與不可變對象詳細(xì)教程的文章就介紹到這了,更多相關(guān)Python可變對象與不可變對象內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python各類經(jīng)緯度轉(zhuǎn)換的實例代碼
這篇文章主要介紹了python各類經(jīng)緯度轉(zhuǎn)換的實例代碼,非常不錯,具有一定的參考借鑒價值,具有一定的參考借鑒價值,需要的朋友可以參考下2019-08-08
Python進(jìn)度條可視化之監(jiān)測程序運(yùn)行速度
Tqdm是一個快速,可擴(kuò)展的Python進(jìn)度條,可以在Python長循環(huán)中添加一個進(jìn)度提示信息,用戶只需要封裝任意的迭代器即可。本文就主要介紹了通過進(jìn)度條檢測程序運(yùn)行速度,感興趣的同學(xué)可以學(xué)習(xí)一下2021-12-12
Python實現(xiàn)Smtplib發(fā)送帶有各種附件的郵件實例
本篇文章主要介紹了Python實現(xiàn)Smtplib發(fā)送帶有各種附件的郵件實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06
利用Python+requests批量檢測網(wǎng)站是否可訪問的實戰(zhàn)指南
在日常開發(fā)、運(yùn)維、測試和數(shù)據(jù)采集中,我們經(jīng)常需要判斷一批網(wǎng)站或接口是否能夠正常訪問,如果一個一個用瀏覽器打開,不僅效率低,而且很難記錄結(jié)果,使用 Python 的 requests 庫,就可以把這個過程自動化,本文會用一個完整案例演示,需要的朋友可以參考下2026-05-05
VScode連接遠(yuǎn)程服務(wù)器上的jupyter notebook的實現(xiàn)
這篇文章主要介紹了VScode連接遠(yuǎn)程服務(wù)器上的jupyter notebook的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
python GUI庫圖形界面開發(fā)之PyQt5表單布局控件QFormLayout詳細(xì)使用方法與實例
這篇文章主要介紹了python GUI庫圖形界面開發(fā)之PyQt5布局控件QFormLayout詳細(xì)使用方法與實例,需要的朋友可以參考下2020-03-03

