【Python 核心概念】深入理解可變與不可變類型

文章目錄

    • 一、故事從變量賦值說起
    • 二、不可變類型 (Immutable Types)
    • 三、可變類型 (Mutable Types)
    • 四、一個常見的陷阱:當元組遇到列表
    • 五、為什么這個區別如此重要?
      • 1. 函數參數的傳遞
      • 2. 字典的鍵 (Dictionary Keys)
      • 3. 函數的默認參數陷阱
    • 六、進階話題與擴展
      • 1. 淺拷貝 vs. 深拷貝:`copy` 與 `deepcopy`
      • 2. `+=` 運算符:可變與不可變對象的差異
      • 3. CPython 的對象緩存機制
      • 4. 并發中的可變對象
      • 5. 凍結(只讀)數據結構
      • 6. 性能小貼士
    • 總結

在 Python 的學習和實踐中,有一個核心概念是繞不開的,那就是"可變"(Mutable)與"不可變"(Immutable)類型。剛開始,你可能覺得這只是個定義問題,但隨著你寫出更復雜的程序,你會發現,能否深刻理解這兩者的區別,直接決定了你的代碼是否健壯、高效,以及是否會踩到一些意想不到的"坑"。

這篇文章將帶你由淺入深,徹底搞懂這個關鍵概念。

一、故事從變量賦值說起

在 Python 中,我們常說"變量是貼在對象上的標簽"。理解這句話是后續一切的基礎。

當你寫下 x = 100 時,Python 做了兩件事:

  1. 在內存中創建了一個代表數字 100 的對象。
  2. 創建了一個名為 x 的變量(標簽),然后把它"貼"到 100 這個對象上。

那么,當我們"修改"變量時,會發生什么呢?這就要看對象的類型了。

二、不可變類型 (Immutable Types)

顧名思義,不可變類型的對象,其值在創建后就不能被改變。任何對它的"修改"操作,實際上都會創建一個全新的對象。

常見的不可變類型包括:

  • 數字: int, float, bool
  • 字符串: str
  • 元組: tuple
  • 凍結集合: frozenset

讓我們用代碼和內存地址 id() 來眼見為實。

示例 1: 字符串 str

my_string = "hello"
print(f"初始字符串: '{my_string}', 內存地址: {id(my_string)}")# 嘗試"修改"字符串
my_string = my_string + " world"print(f"修改后字符串: '{my_string}', 內存地址: {id(my_string)}")

輸出:

初始字符串: 'hello', 內存地址: 4389754112
修改后字符串: 'hello world', 內存地址: 4389754224

看到了嗎?內存地址變了!Python 并沒有修改原來的 "hello" 對象,而是創建了一個全新的 "hello world" 對象,然后把 my_string 這個標簽從舊對象身上撕下來,貼到了新對象上。

三、可變類型 (Mutable Types)

與不可變類型相反,可變類型的對象,其值可以在創建后被原地修改,而不需要創建新對象。

常見的可變類型包括:

  • 列表: list
  • 字典: dict
  • 集合: set
  • 字節數組: bytearray

示例 2: 列表 list

my_list = [1, 2, 3]
print(f"初始列表: {my_list}, 內存地址: {id(my_list)}")# 嘗試修改列表
my_list.append(4)print(f"修改后列表: {my_list}, 內存地址: {id(my_list)}")

輸出:

初始列表: [1, 2, 3], 內存地址: 4510696320
修改后列表: [1, 2, 3, 4], 內存地址: 4510696320

內存地址完全沒變!append 操作是在原始列表對象上直接進行的修改。my_list 這個標簽自始至終都貼在同一個對象上。

四、一個常見的陷阱:當元組遇到列表

元組 tuple 是不可變的,對吧?這意味著我們不能增加或刪除它的元素。但如果元組里包含了可變類型的對象(比如列表),情況就變得有趣了。

# 元組本身是不可變的
my_tuple = (1, 2, ['a', 'b'])print(f"初始元組: {my_tuple}, 內存地址: {id(my_tuple)}")# 嘗試修改元組中的列表
my_tuple[2].append('c')print(f"修改后元組: {my_tuple}, 內存地址: {id(my_tuple)}")# 嘗試直接修改元組元素(這會報錯)
# my_tuple[0] = 99 # TypeError: 'tuple' object does not support item assignment

輸出:

初始元組: (1, 2, ['a', 'b']), 內存地址: 4474840192
修改后元組: (1, 2, ['a', 'b', 'c']), 內存地址: 4474840192

元組的內存地址沒變,但它里面的列表內容卻實實在在地改變了。

結論:不可變性指的是對象本身的結構固定。對于元組來說,是它所包含的元素的"引用"不可變。它引用的那個列表還是那個列表(內存地址沒變),但列表自身的內容是可以被修改的。

五、為什么這個區別如此重要?

理解可變與不可變,在實際編程中至關重要,尤其體現在以下幾個方面:

1. 函數參數的傳遞

在 Python 中,函數參數傳遞的是對象的引用。

  • 如果傳遞的是不可變對象,你在函數內部無法修改原始調用者的變量。
  • 如果傳遞的是可變對象,你在函數內部的修改會直接影響到原始對象。
def process_data(immutable_str, mutable_list):immutable_str = "changed"mutable_list.append(99)print(f"函數內部: str='{immutable_str}', list={mutable_list}")s = "original"
l = [1, 2]process_data(s, l)print(f"函數外部: str='{s}', list={l}")

輸出:

函數內部: str='changed', list=[1, 2, 99]
函數外部: str='original', list=[1, 2, 99]

看到結果了嗎?字符串 s 沒變,但列表 l 被永久地改變了。

2. 字典的鍵 (Dictionary Keys)

字典的鍵必須是不可變類型

這是因為字典的查找效率極高,其內部依賴于對鍵進行哈希運算(hash())。哈希值要求在對象的生命周期內保持不變。

  • 不可變對象的值固定,哈希值也固定。
  • 可變對象的值可以變,如果允許它當鍵,它的哈希值也可能變,整個字典的結構就會崩潰。
my_dict = {}
my_dict["key"] = "value"  # 字符串可以當鍵
my_dict[123] = "value"    # 整數可以當鍵
my_dict[(1, 2)] = "value" # 元組可以當鍵# 嘗試用列表當鍵
try:my_dict[[1, 2]] = "value"
except TypeError as e:print(e) # 輸出: unhashable type: 'list'

3. 函數的默認參數陷阱

這是一個經典的面試題,也是新手最容易犯的錯誤:永遠不要使用可變類型作為函數的默認參數

def add_item(item, item_list=[]):item_list.append(item)return item_list# 第一次調用
print(add_item(1)) # 輸出: [1]# 第二次調用
print(add_item(2)) # 輸出: [1, 2] (你可能期望的是 [2])# 第三次調用
print(add_item(3)) # 輸出: [1, 2, 3]

原因:函數的默認參數 item_list=[] 只在函數定義時被創建一次。后續所有不提供 item_list 參數的調用,都共享著同一個列表對象。

正確做法:

def add_item_fixed(item, item_list=None):if item_list is None:item_list = [] # 在函數體內創建新列表item_list.append(item)return item_list

六、進階話題與擴展

1. 淺拷貝 vs. 深拷貝:copydeepcopy

Python 標準庫 copy 模塊提供兩種復制策略:

  • 淺拷貝 (copy.copy):僅復制最外層容器,新容器內部仍引用原有子對象。
  • 深拷貝 (copy.deepcopy):遞歸復制整棵對象圖,確保任何層級的修改互不影響。
import copya = [1, [2, 3]]
b = copy.copy(a)      # 淺拷貝
c = copy.deepcopy(a)  # 深拷貝a[1].append(4)
print(b)  # [1, [2, 3, 4]]  —— 受影響
print(c)  # [1, [2, 3]]     —— 不受影響

2. += 運算符:可變與不可變對象的差異

對于不可變對象(如 str, tuple),x += y 會創建 新對象;而對 list 等可變對象,+= 會就地修改。

s = "abc"
print(id(s))s += "d"
print(id(s))  # 地址變化,說明創建了新對象lst = [1, 2]
print(id(lst))lst += [3]
print(id(lst))  # 地址不變,說明原地修改

3. CPython 的對象緩存機制

為了性能,CPython 會緩存 小整數 (-5~256)部分短字符串。因此下面代碼在 CPython 中可能打印 True,并不代表語言層面對這些對象做了特殊對待,而是實現細節:

a = 100
b = 100
print(a is b)  # True (CPython)

其它解釋器(PyPy、Jython 等)不一定有相同表現,因此不要依賴該特性來做邏輯判斷。

4. 并發中的可變對象

在多線程 / 協程場景下,共享可變對象必須采用同步原語,否則會產生競態條件或數據損壞;不可變對象天然只讀,可安全共享。

from threading import Lockcounter = 0
lock = Lock()def inc():global counterwith lock:counter += 1

5. 凍結(只讀)數據結構

  • frozenset:不可變集合,可作為字典鍵;
  • types.MappingProxyType:為字典提供只讀視圖;
  • 三方庫 immutables.Map:高性能、結構共享的持久化不可變映射。

6. 性能小貼士

  • 頻繁拼接字符串時,先收集到 list 再使用 ''.join(chunks),可避免創建大量中間對象;
  • 對可變對象使用就地修改操作(如 list.append, 切片賦值)通常更節省內存、提升性能。

總結

特性不可變類型 (Immutable)可變類型 (Mutable)
定義創建后值不能被改變創建后值可以被原地修改
示例int, str, tuple, frozensetlist, dict, set
修改行為創建新對象,變量指向新對象在原對象上修改,變量指向不變
字典鍵可以作為字典的鍵不可以作為字典的鍵
函數傳參函數內修改不影響外部原變量函數內修改會影響外部原變量

掌握 Python 的可變與不可變類型,是寫出清晰、可預測且無 Bug 代碼的基石。希望這篇文章能讓你對這個概念有更深入的理解。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88366.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88366.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88366.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

wpf使用webview2顯示網頁內容(最低兼容.net framework4.5.2)

wpf使用webview2顯示網頁內容(最低兼容.net framework4.5.2 一、核心功能與架構混合開發支持?進程隔離模型?通信機制?二、核心優勢性能與兼容性?跨平臺部署?開發效率?安全機制?三、適用場景四、開發部署要點WebView2 是微軟推出的現代瀏覽器控件,基于 Chromium 內核的 …

MySQL斷開連接后無法正常啟動解決記錄

問題現象 夜里23點MySQL在還原備份的時候斷開連接,嘗試重啟,表面上是運行中實際上無法通過命令端連接,無法正常啟動。 問題檢查 可以使用 systemctl start mysql 但是沒有監聽 3306端口 mysql -ucosmic -p 提示無法找到socket文件 刪除原先的…

隧道安全監測系統的應用意義

隨著我國交通基礎設施建設的快速發展,公路、鐵路及城市地鐵隧道數量不斷增加,隧道安全問題日益凸顯。隧道作為地下封閉空間,受地質條件、施工質量、運營環境等多因素影響,易出現結構變形、滲漏水、襯砌開裂等安全隱患。一旦發生事…

前端UI邏輯復雜可以用什么設計模式

中介者模式 當UI組件間存在復雜交互或多個組件需共享狀態時,中介者模式能集中管理事件分發和狀態更新,減少組件間的直接依賴,提升解耦性。 vue實現中介者模式 在Vue中實現中介者模式,你可以通過創建一個全局的事件中心&#xff08…

WIFI協議全解析05:WiFi的安全機制:IoT設備如何實現安全連接?

🔐 WiFi的安全機制:IoT設備如何實現安全連接?“我的設備明明連上WiFi了,為什么還是能被‘蹭網’?” “WPA3 是什么?ESP32 支持嗎?” “我做了MQTT加密就算安全了嗎?”IoT設備連接WiF…

HTTP 請求體類型詳解:選擇最適合的數據提交格式

HTTP 請求體類型詳解:選擇最適合的數據提交格式 🚀 本文全面解析 HTTP 請求中不同 Content-Type 的適用場景、數據結構與優劣勢,幫助開發者高效選擇數據傳輸方案。 📌 目錄 核心請求體類型對比詳細類型解析最佳實踐指南總結 &am…

C語言 | 函數核心機制深度解構:從底層架構到工程化實踐

個人主頁-愛因斯晨 文章專欄-C語言 引言 最近偷懶了,迷上了三國和李賀。給大家分享一下最喜歡的一句詩:吾不識青天高黃地厚,唯見月寒日暖來煎人壽。我還不是很理解27歲的李賀,如何寫出如此絕筆。 正文開始,今天我們…

uniapp真機調試“沒有檢測到設備,請插入設備或啟動模擬器后點擊刷新再試”

當真機調試,運行到安卓 APP基座 時,有時會檢測不到設備,顯示下面的問題:此時,可以通過下面的幾種方法進行排查:1.在手機中找到“開發者選項”選項(可在設置中搜索,如搜索不到&#x…

使用langchain連接llama.cpp部署的本地deepseek大模型開發簡單的LLM應用

langchain是一個基于python實現的開源LLM開發框架,llama.cpp是一個基于C框架可以在本地部署大模型并開放服務端接口開放給外部應用使用。 本文結合langchain和llama.cpp,在本地部署輕量級的deepseek大模型,并構建一個簡單的鏈式LLM應用&…

Serverless 數據庫來了?無服務器數據庫 vs 傳統數據庫有何不同?

隨著云計算技術的迅猛發展,無服務器(Serverless)架構逐漸成為一種主流趨勢。其中,Serverless 數據庫作為云原生應用的重要組成部分,為開發者提供了前所未有的靈活性和成本效益。相比傳統的數據庫管理方式,S…

【讀書筆記】如何畫好架構圖:架構思維的三大底層邏輯

【讀書筆記】如何畫好架構圖:架構思維的三大底層邏輯 架構圖并非技術人的“畫功比拼”,而是一個團隊、一個系統、一次項目從混沌走向清晰的關鍵抓手。它是系統的視覺語言,是讓技術人員、產品經理、運營甚至老板都能站在統一上下文下討論的“…

Maven 編譯過程中發生了 Java Heap Space 內存溢出(OutOfMemoryError)

這個是我最近遇到的,因為本人最近換了電腦,這個電腦的前任是配置好了環境,但是當我用這個環境去做另外一個項目的時候,在maven構建war和jar包的時候,報了這個內存溢出mvn clean install 就給我報錯了[ERROR] Failed to…

C++ 模板參數展開

C 模板參數展開一、獲取可變參數大小二、通過模版循環繼承的方式來展開可變參數三、改用Using去實現循環繼承一、獲取可變參數大小 背景&#xff1a; FLen<int, char, long> Len; 我想要獲取模板參數類型的總大小 template<typename T,typename ...ParamTypes> c…

零基礎入門物聯網-遠程門禁開關:云平臺創建

一、 onenet云平臺注冊創建 遠程開關的信息傳輸依賴云平臺&#xff0c;本教程以 OneNET - 中國移動物聯網開放平臺為例進行操作&#xff0c;具體步驟如下&#xff1a; 1、平臺賬號創建 點擊 OneNET - 中國移動物聯網開放平臺進入官網 點擊頁面中的 “登錄” 按鈕&#xff0c;…

html頁面,當鼠標移開A字標就隱藏顏色框

html頁面代碼&#xff1a;<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>顏色選擇器</title><style>body {font-family: "Microsoft YaHei", sans-serif;padding: 20px;}.c…

保姆級搭建harbor私有倉庫與docker-ce教程與使用教程

搭建harbor倉庫[rootharbor ~]# vim cat /etc/host192.168.121.12 harbor[rootharbor ~]# vim /etc/hostnameharbor導入 harbor 項目鏡像[rootharbor ~]# tar -zxf harbor-v2.9.2.tgz -C /usr/local/[rootharbor ~]# cd /usr/local/harbor[rootharbor harbor]# docker load -i…

【Linux】Rocky Linux 安裝 Docker 與 Docker-Compose

Docker 安裝步驟 1. 安裝必要的軟件包 sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo2. 安裝Docker sudo yum install docker-ce docker-ce-cli containerd.io如果出現 SSL 證書錯誤&#xf…

揭示獨特模式:Elasticsearch 中 significant terms 聚合指南

作者&#xff1a;來自 Elastic Alexander Dvila 了解如何使用 significant terms 聚合來發現你數據中的洞察。 更多閱讀&#xff1a;Elasticsearch&#xff1a;significant terms aggregation Elasticsearch 擁有大量新功能&#xff0c;可以幫助你為你的使用場景構建最佳搜索解…

pandas.DataFrame中axis參數

明確axis0與axis1的區別和聯系&#xff0c; 假設有一個 DataFrame&#xff1a;indexAB012134axis0&#xff08;沿行方向&#xff09;&#xff1a; 操作會垂直向下進行&#xff0c;對每一列單獨處理。 例如&#xff1a;df.sum(axis0) 會對列 A 和列 B 分別求和&#xff0c;結果是…

深度學習 最簡單的神經網絡 線性回歸網絡

用最簡單的線性模型講清 神經網絡 訓練全流程,讓你 5 分鐘看懂AI 是怎么學會預測的 ?? 1 真實神經元結構 ?? 真實神經元包括: 樹突 接收其他神經元傳來的電信號(輸入)。 細胞核 負責整合輸入信號并產生動作電位。 軸突 傳導動作電位到下一個神經元。 突觸 釋放神經遞質…