例屬性:從原理到實(shí)戰(zhàn))
目錄一、兩種屬性的區(qū)別二、屬性讀取演示完整演示代碼三、類屬性全局修改四、實(shí)例賦值的坑五、屬性查找規(guī)則六、完整流程示例七、del 刪除屬性八、實(shí)戰(zhàn)統(tǒng)計(jì)實(shí)例數(shù)量九、動(dòng)態(tài)添加類屬性十、兩類屬性對(duì)比十一、易錯(cuò)清單十二、本章小結(jié)上一章我們學(xué)會(huì)了基礎(chǔ)的類、實(shí)例、__init__構(gòu)造方法與實(shí)例屬性我們知道self.xxx是每一個(gè)對(duì)象獨(dú)有的數(shù)據(jù)每只小貓有自己的名字、毛色每臺(tái)手機(jī)有專屬品牌、售價(jià)互相不會(huì)干擾。但現(xiàn)實(shí)里還有一類數(shù)據(jù)屬于同一類事物全體共用不需要每個(gè)實(shí)例單獨(dú)存一份。比如所有手機(jī)都屬于“智能手機(jī)”這個(gè)大類這個(gè)分類標(biāo)簽不用蘋果、小米各自存一遍放在模板里統(tǒng)一管理就足夠。今天我們就來拆解兩種屬性的核心區(qū)別實(shí)例屬性對(duì)象獨(dú)有和類屬性全類共享理清讀取、修改的全部規(guī)則避開極易踩坑的同名賦值陷阱。一、兩種屬性的區(qū)別我們以手機(jī)產(chǎn)品模板來舉例直觀看懂兩種數(shù)據(jù)的定位class Phone: # 類屬性全品類手機(jī)共用分類標(biāo)簽 category 智能手機(jī) def __init__(self, brand, price): # 實(shí)例屬性單臺(tái)手機(jī)獨(dú)有的信息 self.brand brand self.price price1. 類屬性 category相當(dāng)于產(chǎn)品大類統(tǒng)一標(biāo)識(shí)所有手機(jī)共用這一條內(nèi)容只保存一份。如果修改這個(gè)分類名稱所有手機(jī)讀取到的標(biāo)簽都會(huì)同步更新。2. 實(shí)例屬性 brand、price是單臺(tái)設(shè)備專屬信息華為、小米各自的品牌、價(jià)格互不干擾修改其中一臺(tái)不會(huì)影響其他設(shè)備。簡(jiǎn)單總結(jié)實(shí)例屬性 每個(gè)對(duì)象私有信息彼此隔離類屬性 全類共享信息一處修改全部對(duì)象同步生效。二、屬性讀取演示完整演示代碼class Phone(object): # 定義類屬性放在所有函數(shù)外部 category 智能手機(jī) def __init__(self, brand, price): # self開頭為實(shí)例屬性每個(gè)實(shí)例獨(dú)立 self.brand brand self.price price 新建兩臺(tái)手機(jī)實(shí)例 p1 Phone(蘋果, 5999) p2 Phone(小米, 3999) 讀取實(shí)例專屬數(shù)據(jù)兩臺(tái)結(jié)果不一樣 print(p1.brand) print(p2.brand) print(p1.price) print(p2.price) 讀取共享類屬性兩種寫法都可以 寫法1直接使用類名讀取規(guī)范推薦 print(Phone.category) 寫法2通過實(shí)例讀取結(jié)果完全相同 print(p1.category) print(p2.category)運(yùn)行輸出結(jié)果蘋果 小米 5999 3999 智能手機(jī) 智能手機(jī) 智能手機(jī)能看出不管通過類還是任意實(shí)例拿到的類屬性數(shù)值完全一致。三、類屬性全局修改如果使用「類名.屬性名」修改類屬性所有創(chuàng)建好的實(shí)例再讀取時(shí)都會(huì)拿到更新后的內(nèi)容。下面給出完整可運(yùn)行的代碼包含完整的Phone類定義class Phone: # 類屬性全品類手機(jī)共用分類標(biāo)簽 category 智能手機(jī) def __init__(self, brand, price): # 實(shí)例屬性單臺(tái)手機(jī)獨(dú)有的信息 self.brand brand self.price price p1 Phone(蘋果, 5999) p2 Phone(小米, 3999) 通過類統(tǒng)一修改共享分類 Phone.category 5G智能手機(jī) 兩臺(tái)設(shè)備讀取到的內(nèi)容同步變化 print(p1.category) print(p2.category) print(Phone.category)輸出內(nèi)容統(tǒng)一變?yōu)?G智能手機(jī)實(shí)現(xiàn)全局統(tǒng)一修改。四、實(shí)例賦值的坑很多初學(xué)者會(huì)直接用實(shí)例修改類屬性名稱這種操作不會(huì)改動(dòng)全局共享數(shù)據(jù)只會(huì)給當(dāng)前實(shí)例新增一份同名私有數(shù)據(jù)造成數(shù)據(jù)隔離。沿用第二節(jié)的Phone類只貼改動(dòng)片段p1 Phone(蘋果, 5999) p2 Phone(小米, 3999) # 錯(cuò)誤操作通過實(shí)例給 category 賦值 p1.category 老年機(jī) # p1 只會(huì)讀取自己新增的私有數(shù)據(jù) print(p1.category) # 類和其他實(shí)例不受任何影響 print(Phone.category) print(p2.category)運(yùn)行輸出老年機(jī) 智能手機(jī) 智能手機(jī)底層邏輯實(shí)例讀取數(shù)據(jù)時(shí)會(huì)優(yōu)先查找自身攜帶的屬性只有自身不存在對(duì)應(yīng)字段時(shí)才會(huì)讀取類里的共享數(shù)據(jù)。一旦給實(shí)例新增同名屬性全局共享內(nèi)容就不再對(duì)它生效。五、屬性查找規(guī)則為什么實(shí)例賦值會(huì)「隔離」出私有數(shù)據(jù)答案藏在 Python 的屬性查找順序里。這是理解兩種屬性差異的核心難點(diǎn)務(wù)必掌握。屬性查找的完整底層規(guī)則Python實(shí)例讀取屬性時(shí)會(huì)按照固定順序逐層查找先在當(dāng)前實(shí)例自身查找該屬性找到就直接使用實(shí)例自身不存在該屬性再去所屬類中查找類屬性類中也沒有就拋出AttributeError報(bào)錯(cuò)。配套演示報(bào)錯(cuò)代碼class Phone: category 手機(jī) p Phone(小米) print(p.price) # 實(shí)例、類都無 price直接報(bào)錯(cuò)運(yùn)行這段代碼會(huì)拋出AttributeError: Phone object has no attribute price因?yàn)閷?shí)例p自身沒有price類Phone里也沒有price兩層都找不到最終只能報(bào)錯(cuò)。六、完整流程示例配套簡(jiǎn)短代碼完整走完定義、讀取、修改、隔離全過程。沿用第二節(jié)的Phone類只貼改動(dòng)片段p1 Phone(apple) p2 Phone(xiaomi) # 初始共享標(biāo)簽 print(Phone.category) print(p1.category) print(p2.category) # 類統(tǒng)一修改標(biāo)簽 Phone.category 手機(jī) print(p1.category) print(Phone.category) print(p2.category) # 實(shí)例創(chuàng)建獨(dú)立同名屬性 p1.category 5G手機(jī) print(p1.category) print(Phone.category) print(p2.category)七、del 刪除屬性除了讀取和修改屬性還可以用del關(guān)鍵字刪除。刪除實(shí)例獨(dú)有屬性和刪除類全局屬性效果完全不同下面分別演示1. 刪除實(shí)例獨(dú)有屬性實(shí)例屬性是每個(gè)對(duì)象單獨(dú)持有的數(shù)據(jù)用del 實(shí)例.屬性名可以把它從當(dāng)前實(shí)例上移除其他實(shí)例不受影響class Phone: def __init__(self, brand, price): self.brand brand self.price price p1 Phone(蘋果, 5999) p2 Phone(小米, 3999) # 刪除 p1 的實(shí)例屬性 price del p1.price # p1 再讀取 price 會(huì)報(bào)錯(cuò) print(p1.price) # AttributeError # p2 的 price 不受影響 print(p2.price) # 39992. 刪除類全局屬性類屬性屬于整個(gè)類共享用del 類名.屬性名刪除后所有實(shí)例再讀取該屬性都會(huì)報(bào)錯(cuò)class Phone: category 智能手機(jī) p1 Phone(蘋果) p2 Phone(小米) # 刪除類屬性 category del Phone.category # 所有實(shí)例再讀取都會(huì)報(bào)錯(cuò) print(p1.category) # AttributeError print(p2.category) # AttributeError3. 對(duì)比del 實(shí)例同名屬性后會(huì)重新讀取類屬性結(jié)合前面學(xué)過的查找規(guī)則如果實(shí)例自身有同名屬性刪除它之后實(shí)例會(huì)重新回到類里讀取共享的類屬性class Phone: category 智能手機(jī) p1 Phone(蘋果) # 給實(shí)例新增同名屬性覆蓋類屬性 p1.category 老年機(jī) print(p1.category) # 老年機(jī)讀取實(shí)例自身 # 刪除實(shí)例自帶的 category del p1.category # 實(shí)例自身沒有了重新讀取類屬性 print(p1.category) # 智能手機(jī)原因解釋del p1.category只刪除實(shí)例自身新增的同名屬性類里共享的category仍然存在。刪除后實(shí)例再讀取時(shí)自身找不到該屬性就會(huì)按查找順序回到類中讀取所以輸出恢復(fù)為智能手機(jī)。八、實(shí)戰(zhàn)統(tǒng)計(jì)實(shí)例數(shù)量類屬性最常用的場(chǎng)景是全局統(tǒng)計(jì)記錄一共創(chuàng)建了多少個(gè)實(shí)例不需要為每一個(gè)對(duì)象單獨(dú)記錄數(shù)字。沿用第二節(jié)的Phone類只貼改動(dòng)片段class Phone: # 類屬性全局計(jì)數(shù)器所有實(shí)例共用 count 0 category 智能手機(jī) def __init__(self, brand): # 實(shí)例屬性單臺(tái)手機(jī)獨(dú)有的信息 self.brand brand # 每創(chuàng)建一臺(tái)設(shè)備總數(shù) 1 Phone.count 1 連續(xù)創(chuàng)建三臺(tái)設(shè)備 p1 Phone(華為) p2 Phone(vivo) p3 Phone(oppo) 讀取全局統(tǒng)計(jì)數(shù)量 print(已生產(chǎn)設(shè)備總數(shù)量, Phone.count) print(設(shè)備統(tǒng)一分類, Phone.category)運(yùn)行結(jié)果已生產(chǎn)設(shè)備總數(shù)量 3 設(shè)備統(tǒng)一分類智能手機(jī)反面錯(cuò)誤案例不要用self.count 1做全局計(jì)數(shù)很多新手會(huì)寫成self.count 1以為這樣就能實(shí)現(xiàn)全局計(jì)數(shù)其實(shí)這是錯(cuò)誤寫法只會(huì)給當(dāng)前實(shí)例新增一份獨(dú)立的count全局共享的count永遠(yuǎn)保持初始值class Phone: count 0 def __init__(self, brand): self.brand brand self.count 1 # 錯(cuò)誤寫法只會(huì)給實(shí)例新增 count全局 count 永遠(yuǎn)為 0 p1 Phone(華為) print(Phone.count) # 輸出 0計(jì)數(shù)失效原因解釋self.count 1等價(jià)于先讀取再賦值由于實(shí)例自身沒有count讀取時(shí)拿到的是類里的共享值 0但賦值時(shí)卻是在當(dāng)前實(shí)例上新建了一個(gè)同名屬性count并不會(huì)修改類共享的count。所以全局計(jì)數(shù)必須寫成Phone.count 1直接通過類名修改類屬性才能讓所有實(shí)例同步看到更新。九、動(dòng)態(tài)添加類屬性前面看到的類屬性都是寫在class代碼塊內(nèi)部其實(shí)類屬性并不強(qiáng)制要求定義在類里。代碼任意位置只要通過「類名.屬性名」賦值就能給類動(dòng)態(tài)新增一個(gè)類屬性所有實(shí)例同樣可以讀取到class Phone: pass 類外部動(dòng)態(tài)添加類屬性 Phone.count 0 p1 Phone() p2 Phone() 所有實(shí)例都能讀取到動(dòng)態(tài)新增的類屬性 print(Phone.count) # 0 print(p1.count) # 0 print(p2.count) # 0運(yùn)行結(jié)果0 0 0原因解釋類屬性本質(zhì)上就是掛在類對(duì)象上的一個(gè)名字只要通過類名.屬性名 值賦值無論寫在類定義內(nèi)部還是外部效果完全一樣。這也說明類屬性不局限于寫在類定義里代碼任意位置通過類名都能新增進(jìn)一步拓展了對(duì)類屬性的認(rèn)知。業(yè)務(wù)場(chǎng)景舉例程序運(yùn)行中動(dòng)態(tài)新增分類標(biāo)簽。比如電商后臺(tái)在運(yùn)營過程中新增了一個(gè)「限時(shí)秒殺」分類不需要修改類定義代碼直接通過Goods.category 限時(shí)秒殺動(dòng)態(tài)掛到類上所有已創(chuàng)建的商品實(shí)例立刻都能讀取到這個(gè)新標(biāo)簽無需逐個(gè)對(duì)象賦值。十、兩類屬性對(duì)比到這里實(shí)例屬性和類屬性的核心差異已經(jīng)全部講完。下面用一張對(duì)比表把兩者的定義位置、存儲(chǔ)方式、讀取方式、修改影響和刪除效果一次性梳理清楚方便復(fù)習(xí)和查閱。對(duì)比維度實(shí)例屬性類屬性定義位置在__init__方法內(nèi)通過self.屬性名定義在類代碼塊內(nèi)、所有方法外部直接屬性名 值定義存儲(chǔ)方式每個(gè)實(shí)例單獨(dú)保存一份互不干擾整個(gè)類只保存一份所有實(shí)例共享讀取方式實(shí)例.屬性名類名.屬性名或?qū)嵗?屬性名均可通過類修改不適用類無法直接修改實(shí)例屬性類名.屬性名 新值所有實(shí)例同步更新通過實(shí)例賦值正常修改當(dāng)前實(shí)例自己的數(shù)據(jù)不會(huì)修改類屬性只會(huì)給當(dāng)前實(shí)例新增同名私有屬性刪除方式del 實(shí)例.屬性名只影響當(dāng)前實(shí)例del 類名.屬性名所有實(shí)例再讀取都會(huì)報(bào)錯(cuò)典型應(yīng)用每臺(tái)手機(jī)的品牌、價(jià)格等專屬信息全品類統(tǒng)一分類標(biāo)簽、全局計(jì)數(shù)器等共享數(shù)據(jù)一句話記憶實(shí)例屬性管「?jìng)€(gè)體差異」類屬性管「群體共性」。寫代碼時(shí)先想清楚這份數(shù)據(jù)是每個(gè)對(duì)象各自持有還是全體對(duì)象共用再?zèng)Q定用哪種屬性就能避免絕大多數(shù)混淆。十一、易錯(cuò)清單本章涉及多個(gè)容易踩坑的細(xì)節(jié)下面把高頻易錯(cuò)點(diǎn)集中列出來配合正確寫法一起對(duì)照易錯(cuò)點(diǎn) 1用實(shí)例賦值想修改類屬性錯(cuò)誤寫法p1.category 老年機(jī)以為能改全局分類。正確認(rèn)知這只會(huì)給p1新增一份同名私有屬性類和其他實(shí)例完全不受影響。要修改類屬性必須用Phone.category 老年機(jī)。易錯(cuò)點(diǎn) 2用self.count 1做全局計(jì)數(shù)錯(cuò)誤寫法在__init__里寫self.count 1以為能統(tǒng)計(jì)實(shí)例總數(shù)。正確寫法必須寫成Phone.count 1直接通過類名修改類屬性才能讓所有實(shí)例共享同一個(gè)計(jì)數(shù)器。易錯(cuò)點(diǎn) 3忘記屬性查找順序?qū)嵗x取屬性時(shí)永遠(yuǎn)先找自身再找類。一旦實(shí)例有了同名屬性類里的共享值就被「屏蔽」了。刪除實(shí)例同名屬性后又會(huì)重新讀取到類屬性。易錯(cuò)點(diǎn) 4刪除類屬性導(dǎo)致全局報(bào)錯(cuò)del Phone.category會(huì)刪除整個(gè)類共享的屬性之后所有實(shí)例再讀取category都會(huì)拋出AttributeError。刪除類屬性前務(wù)必確認(rèn)沒有其他地方還在使用它。易錯(cuò)點(diǎn) 5混淆「讀取」和「賦值」的路徑讀取時(shí)p1.category和Phone.category結(jié)果一樣但賦值時(shí)兩者效果完全不同前者新增實(shí)例屬性后者修改類屬性。讀取和賦值要分開記憶不能混為一談。十二、本章小結(jié)本章圍繞實(shí)例屬性和類屬性系統(tǒng)梳理了從定義、讀取、修改到刪除的完整規(guī)則核心要點(diǎn)可以概括為以下幾條實(shí)例屬性是每個(gè)對(duì)象獨(dú)有的數(shù)據(jù)通過self.屬性名定義彼此隔離互不影響。類屬性是全類共享的數(shù)據(jù)定義在類代碼塊內(nèi)、方法外部一處修改所有實(shí)例同步生效。讀取規(guī)則實(shí)例讀取屬性時(shí)先找自身再找類兩層都沒有就拋出AttributeError。修改規(guī)則通過類名修改類屬性才能全局生效通過實(shí)例賦值只會(huì)新增同名私有屬性不會(huì)改動(dòng)類屬性。刪除規(guī)則del 實(shí)例.屬性名只影響當(dāng)前實(shí)例del 類名.屬性名影響所有實(shí)例。實(shí)用場(chǎng)景類屬性非常適合做全局計(jì)數(shù)器、統(tǒng)一分類標(biāo)簽等共享數(shù)據(jù)實(shí)例屬性適合保存每個(gè)對(duì)象的專屬信息。掌握好「實(shí)例管個(gè)體、類管群體」這條主線再配合屬性查找順序就能在實(shí)戰(zhàn)中準(zhǔn)確判斷該用哪種屬性寫出更清晰、更不易出錯(cuò)的 Python 代碼