Python中訪問類變量與實(shí)例變量的完整步驟
好的,我們來系統(tǒng)地拆解一下 Python 中訪問類變量和實(shí)例變量的完整步驟。這是一個(gè)非常核心且容易混淆的概念。
我會(huì)遵循以下結(jié)構(gòu),由淺入深,確保你徹底理解:
- 核心定義:明確什么是類變量和實(shí)例變量。
- 查找規(guī)則:詳細(xì)解釋 Python 在訪問屬性時(shí)遵循的 “查找鏈”(Lookup Chain)。
- 代碼演練:通過實(shí)例代碼,一步步追蹤查找過程。
- 修改與遮蔽:解釋如何修改變量,以及 “遮蔽”(Shadowing)現(xiàn)象。
- 可變對(duì)象陷阱:探討當(dāng)類變量是可變對(duì)象(如列表、字典)時(shí)的特殊情況。
- 總結(jié)與速查表:提供一個(gè)清晰的總結(jié),幫助你快速?zèng)Q策。
1. 核心定義
類變量 (Class Variable):
- 定義位置:在類的內(nèi)部,但在任何方法的外部。
- 所屬對(duì)象:屬于類本身。
- 共享性:被所有該類的實(shí)例(對(duì)象)共享。一個(gè)實(shí)例對(duì)類變量的修改(在特定條件下)會(huì)影響所有其他實(shí)例。
- 作用:通常用于定義所有實(shí)例都共有的屬性或常量,比如物種(
species)、默認(rèn)計(jì)數(shù)等。
實(shí)例變量 (Instance Variable):
- 定義位置:通常在類的
__init__方法中,以self.開頭。 - 所屬對(duì)象:屬于單個(gè)實(shí)例。
- 唯一性:每個(gè)實(shí)例都有自己獨(dú)立的一份拷貝。修改一個(gè)實(shí)例的實(shí)例變量不會(huì)影響其他實(shí)例。
- 作用:用于存儲(chǔ)每個(gè)實(shí)例獨(dú)有的數(shù)據(jù),比如名字(
name)、年齡(age)等。
代碼示例:
class Dog:
# 類變量:所有狗都共享這個(gè)屬性
species = "Canis lupus familiaris"
count = 0 # 用于計(jì)數(shù)狗的數(shù)量
def __init__(self, name, age):
# 實(shí)例變量:每個(gè)狗對(duì)象都有自己的 name 和 age
self.name = name
self.age = age
Dog.count += 1 # 每次創(chuàng)建實(shí)例,類變量 count 加 12. 查找規(guī)則:屬性查找鏈 (Attribute Lookup Chain)
當(dāng)你使用 instance.attribute 的形式訪問一個(gè)屬性時(shí),Python 會(huì)嚴(yán)格按照以下順序進(jìn)行查找,直到找到為止:
查找實(shí)例自身的命名空間:
- Python 首先會(huì)檢查該實(shí)例對(duì)象是否擁有這個(gè)屬性。你可以通過
instance.__dict__查看實(shí)例的屬性字典。 - 如果在
__dict__中找到了attribute,就直接返回它的值。查找結(jié)束。
查找類的命名空間:
- 如果實(shí)例自身沒有這個(gè)屬性,Python 會(huì)接著檢查該實(shí)例所屬的類是否擁有這個(gè)屬性。你可以通過
Class.__dict__查看類的屬性字典。 - 如果在
__dict__中找到了attribute,就返回它的值。查找結(jié)束。
查找父類的命名空間(繼承鏈):
- 如果類自身也沒有,Python 會(huì)沿著繼承鏈向上,依次查找所有父類和祖先類的
__dict__。 - 這個(gè)過程遵循 Method Resolution Order (MRO),即方法解析順序。
拋出異常:
- 如果沿著整個(gè)繼承鏈都找不到這個(gè)屬性,Python 會(huì)拋出
AttributeError異常。
一句話總結(jié): 先找實(shí)例,再找類,最后找父類。 (instance -> Class -> Parent Class)
3. 代碼演練:追蹤查找過程
讓我們創(chuàng)建一個(gè)實(shí)例,并追蹤訪問不同變量時(shí)的路徑。
# 沿用上面的 Dog 類
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)場景 1:訪問實(shí)例變量 dog1.name
- 查找
dog1的__dict__:{'name': 'Buddy', 'age': 3}。 - 找到了
name,值為'Buddy'。 - 查找停止,返回
'Buddy'。
場景 2:訪問類變量 dog1.species
- 查找
dog1的__dict__:{'name': 'Buddy', 'age': 3}。 - 沒有找到
species。 - 繼續(xù)查找
dog1所屬的類Dog的__dict__。 - 在
Dog.__dict__中找到了species,值為"Canis lupus familiaris"。 - 查找停止,返回
"Canis lupus familiaris"。
場景 3:訪問類變量 Dog.count
- 這里直接從類
Dog開始查找。 - 在
Dog.__dict__中找到了count,值為2。 - 查找停止,返回
2。
場景 4:訪問一個(gè)不存在的變量 dog1.weight
- 查找
dog1的__dict__:{'name': 'Buddy', 'age': 3}。 - 沒有找到
weight。 - 繼續(xù)查找
Dog的__dict__。 - 也沒有找到
weight。 Dog沒有父類(除了object),在object中也找不到。- 查找失敗,拋出
AttributeError: 'Dog' object has no attribute 'weight'。
4. 修改與遮蔽 (Shadowing)
修改變量的行為取決于你通過什么來修改它。
4.1 修改實(shí)例變量
這是最常見的操作,直接在實(shí)例的 __dict__ 中創(chuàng)建或更新屬性。
dog1.age = 4 # 修改 dog1 自己的 age print(dog1.age) # 輸出: 4 print(dog2.age) # 輸出: 5 (不受影響)
4.2 修改類變量
這里有兩種方式,效果完全不同:
方式一:通過類名修改(推薦)
這會(huì)真正地修改類的 __dict__ 中的值,所有實(shí)例都會(huì)受到影響。
Dog.species = "Canis familiaris" # 通過類名修改 print(Dog.species) # 輸出: Canis familiaris print(dog1.species) # 輸出: Canis familiaris (dog1 會(huì)查找到更新后的類變量) print(dog2.species) # 輸出: Canis familiaris (dog2 也會(huì)查找到更新后的類變量)
方式二:通過實(shí)例修改(導(dǎo)致遮蔽)
這不會(huì)修改類變量,而是會(huì)在該實(shí)例自己的 __dict__ 中創(chuàng)建一個(gè)同名的實(shí)例變量。
這個(gè)新創(chuàng)建的實(shí)例變量會(huì) “遮蔽”(shadow)掉類變量,也就是說,之后再通過這個(gè)實(shí)例訪問該變量時(shí),會(huì)直接返回實(shí)例自己的那個(gè),而不再去查找類。
# 此時(shí),Dog.species 是 "Canis familiaris"
print(f"Before shadowing, dog1.__dict__: {dog1.__dict__}") # {'name': 'Buddy', 'age': 4}
dog1.species = "Wolf" # 通過實(shí)例修改,觸發(fā)遮蔽
print(f"After shadowing, dog1.__dict__: {dog1.__dict__}") # {'name': 'Buddy', 'age': 4, 'species': 'Wolf'}
print(f"Dog.__dict__['species']: {Dog.__dict__['species']}") # 'Canis familiaris' (類變量沒變!)
print(dog1.species) # 輸出: Wolf (訪問的是實(shí)例自己的 species)
print(dog2.species) # 輸出: Canis familiaris (dog2 沒有被遮蔽,訪問的仍是類變量)
print(Dog.species) # 輸出: Canis familiaris (類變量本身沒變)5. 可變對(duì)象陷阱 (Mutable Object Pitfall)
這是一個(gè)非常經(jīng)典的面試題和錯(cuò)誤來源。當(dāng)類變量是可變對(duì)象(如列表 list、字典 dict、集合 set)時(shí),通過實(shí)例對(duì)其進(jìn)行原地修改(in-place modification),會(huì)影響所有實(shí)例。
原因:因?yàn)閷?shí)例和類共享同一個(gè)可變對(duì)象的引用。
代碼示例:
class Cat:
# 類變量,一個(gè)空列表
tricks = []
def __init__(self, name):
self.name = name
cat1 = Cat("Kitty")
cat2 = Cat("Lucy")
# 通過實(shí)例 cat1 向 tricks 列表添加元素
cat1.tricks.append("play dead")
# 查看結(jié)果
print(cat1.tricks) # 輸出: ['play dead']
print(cat2.tricks) # 輸出: ['play dead'] (Oh no! cat2 的 tricks 也變了)
print(Cat.tricks) # 輸出: ['play dead'] (實(shí)際上是類變量被修改了)
# 檢查它們是否指向同一個(gè)對(duì)象
print(id(cat1.tricks)) # e.g., 140183245326144
print(id(cat2.tricks)) # e.g., 140183245326144 (和上面的 id 相同)
print(id(Cat.tricks)) # e.g., 140183245326144 (和上面的 id 相同)陷阱分析:cat1.tricks.append(...) 這個(gè)操作,Python 首先在 cat1.__dict__ 中找 tricks,沒找到,然后去 Cat.__dict__ 中找到了 tricks 列表。接著,它對(duì)這個(gè)找到的列表對(duì)象本身執(zhí)行了 append 操作。因?yàn)樗袑?shí)例和類都指向這同一個(gè)列表對(duì)象,所以大家都看到了變化。
如何避免?如果你想讓每個(gè)實(shí)例都有自己獨(dú)立的可變對(duì)象(比如一個(gè)空列表),你應(yīng)該在 __init__ 方法中初始化它。
class Cat:
def __init__(self, name):
self.name = name
# 在實(shí)例化時(shí),為每個(gè)實(shí)例創(chuàng)建一個(gè)獨(dú)立的列表
self.tricks = []
cat1 = Cat("Kitty")
cat2 = Cat("Lucy")
cat1.tricks.append("play dead")
print(cat1.tricks) # 輸出: ['play dead']
print(cat2.tricks) # 輸出: [] (cat2 的列表不受影響)6. 總結(jié)與速查表
| 特性 | 類變量 (Class Variable) | 實(shí)例變量 (Instance Variable) |
|---|---|---|
| 定義位置 | 類內(nèi)部,方法外部 | 通常在 __init__ 方法中,以 self. 開頭 |
| 所屬對(duì)象 | 類 | 實(shí)例 |
| 共享性 | 被所有實(shí)例共享 | 每個(gè)實(shí)例獨(dú)有 |
| 訪問方式 | Class.var 或 instance.var | instance.var |
| 查找順序 | 實(shí)例查找失敗后,再查找類 | 優(yōu)先查找實(shí)例 |
| 修改方式 | Class.var = new_value (影響所有實(shí)例) | instance.var = new_value (只影響當(dāng)前實(shí)例) |
| 通過實(shí)例修改 | instance.var = new_value 會(huì)創(chuàng)建一個(gè)同名的實(shí)例變量,遮蔽類變量 | 直接修改實(shí)例自己的變量 |
| 可變對(duì)象風(fēng)險(xiǎn) | 如果是可變對(duì)象(如列表),通過實(shí)例進(jìn)行原地修改(append等)會(huì)影響所有實(shí)例 | 每個(gè)實(shí)例的可變對(duì)象都是獨(dú)立的,無此風(fēng)險(xiǎn) |
實(shí)踐建議:
- 明確意圖:如果一個(gè)屬性對(duì)所有實(shí)例都通用,用類變量。如果每個(gè)實(shí)例都需要自己獨(dú)立的一份,用實(shí)例變量。
- 修改類變量用類名:為了代碼清晰,避免歧義,修改類變量時(shí)始終使用
Class.variable的形式。 - 警惕可變類變量:除非你明確希望所有實(shí)例共享一個(gè)可變對(duì)象(例如,一個(gè)全局計(jì)數(shù)器),否則不要將可變對(duì)象用作類變量來存儲(chǔ)實(shí)例相關(guān)的狀態(tài)。
希望這個(gè)全面的拆解能幫助你徹底掌握類變量和實(shí)例變量的訪問機(jī)制!
以上就是Python中訪問類變量與實(shí)例變量的完整步驟的詳細(xì)內(nèi)容,更多關(guān)于Python訪問類變量與實(shí)例變量的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Numpy擴(kuò)充矩陣維度(np.expand_dims, np.newaxis)和刪除維度(np.squeeze)的方
這篇文章主要介紹了詳解Numpy擴(kuò)充矩陣維度(np.expand_dims, np.newaxis)和刪除維度(np.squeeze)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
基于Python實(shí)現(xiàn)Markdown編輯器的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何基于Python實(shí)現(xiàn)Markdown編輯器,并且可以左邊寫 Markdown,右邊實(shí)時(shí)變成漂亮網(wǎng)頁,感興趣的小伙伴可以了解下2025-08-08
詳解pandas映射與數(shù)據(jù)轉(zhuǎn)換
這篇文章主要介紹了pandas映射與數(shù)據(jù)轉(zhuǎn)換的相關(guān)資料,幫助大家更好的利用python進(jìn)行數(shù)據(jù)分析,感興趣的朋友可以了解下2021-01-01
python 讀取excel文件生成sql文件實(shí)例詳解
這篇文章主要介紹了python 讀取excel文件生成sql文件實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05
python中is與雙等于號(hào)“==”的區(qū)別示例詳解
Python中有很多運(yùn)算符,下面這篇文章主要給大家介紹了關(guān)于python中is與雙等于號(hào)“==”區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11
Python 類變量和實(shí)例變量的實(shí)現(xiàn)與區(qū)別(附示例)
本文主要介紹了Python 類變量和實(shí)例變量的實(shí)現(xiàn)與區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03

