python面向对象(part2)--封装
學習筆記
 開發工具:Spyder
文章目錄
- 封裝
- 定義
- 私有成員
- 舉個例子1
- 舉個例子2
- 舉個例子3
 
- `__slots__`屬性
- 屬性`@property`
- 案例(可讀、可寫)
- 發現問題
 
 
 
 
 
封裝
定義
- 從數據角度來說
封裝是將一些基本數據類型復合成一個自定義類型,即將數據與對數據的操作封裝起來。
- 從行為角度講
封裝是向類外提供功能,隱藏實現的細節;將復雜的東西藏起來,只給別人提供一種調用。
- 從設計的角度講
①分而治之
 將一個大的需求分為許多類,每個類處理一個獨立的功能。
 拆分的好處:便于分工,便于復用,可擴展性強。
②封裝變化
 對變化的地方(行為上的變化點)獨立封裝(獨立的放到一個類中),避免影響其他類。
③高內聚
 類中各個方法都在完成一項任務(單一職責的類),即一個類可以有多個方法,每個方法都在做一個小功能,但是這些小功能都是在做同一項任務
④低耦合
 類與類的關聯性與依賴性要低(每個類獨立),即當一個類改變時,對其他類的影響要盡量低。
 (最好的低耦合是:一個類被拋棄,其他的類不受其影響)
備注1:最高的內聚,莫過于類中僅包含1個方法,但是將會導致高內聚高耦合。最低的耦合,莫過于類中包含所有方法,但是將會導致低內聚低耦合。
 備注2:舉個例子,電腦硬件(鼠標、鍵盤、內存條…)就具有高度集成化(高內聚),又可插拔(低耦合)的特點。
私有成員
- 作用
私有成員為無需向類外提供訪問的成員,可以通過私有化對成員進行屏蔽。但是在python中,并不是真正的實現了屏蔽,這只是一種障眼法(解釋器會改變雙下劃線開頭的變量名),我們可以通過【_類名__成員名】對私有成員進行訪問。
- 做法
在想要被私有的成員開頭,加上雙下劃線【__】。
舉個例子1
代碼:
class Bunny:def __init__(self, name = "", age = 0):self.name = nameself.__age = agew01 = Bunny() print(w01.__age)結果:
 
 報錯!由報錯信息可知,并不存在變量【__age】,這是因為解釋器改變了【__age】的變量名。
舉個例子2
代碼:
class Bunny:def __init__(self, name = "", age = 0):self.name = nameself.__age = agew01 = Bunny()print(w01._Bunny__age) print(w01.__dict__)結果:
 
舉個例子3
我們再舉一個例子,不僅實現成員的私有,而且在創建實例對象時,對參數進行判斷。
代碼:
class Bunny:def __init__(self, name, age):self.set_name(name)self.set_age(age)def set_name(self, value):self.__name = valuedef get_name(self):return self.__namedef set_age(self, value):if 0<= value < 7:self.__age = valueelse:self.__age = 0print("輸入錯誤")def get_age(self):return self.__ageb01 = Bunny("大白", 5) b01.set_name("小黃") b01.set_age(10) b01.set_age(4) print(b01.get_name()) print(b01.get_age())結果:
 
__slots__屬性
- 作用
限定一個類創建的實例,只能有固定的實例變量,不能再額外添加。
- 語法
- 說明
含有__slots__屬性的類所創建的對象沒有__dict__屬性, 即此實例不用字典來存儲對象的實例屬性。
- 優缺點
優點:訪止用戶因錯寫屬性的名稱而發生程序錯誤。
 缺點:喪失了動態語言可以在運行時為對象添加變量的靈活性。
- 例子(不使用__slots__)
代碼:
class Bunny:def __init__(self, name, age):self.name = nameself.age = agew01 = Bunny("小黃", 6) w01.sex = "公" print(w01.__dict__)結果:
 
- 例子(使用__slots__將實例變量數固定)
代碼:
class Bunny:__slots__ = ("name", "age")def __init__(self, name, age):self.name = nameself.age = agew01 = Bunny("小黃", 6) w01.sex = "公"結果:
 
由結果可知可知,當我們自己多增加一個實例變量時,報錯了,python提示我們,Bunny類中沒有屬性sex.
屬性@property
- 定義及調用
- 說明
①通過兩個公開的屬性,保護一個私有的變量。
 ②@property負責讀取,@屬性名.setter負責寫入
案例(可讀、可寫)
在做這個案例之前我們先回顧一個知識點:
代碼第9行:若直接將list01賦值給list02,則list02得到的是list01所指的對象的地址。
 代碼第10行:若將list01[:]賦值給list03,則list03得到的是list01新創建的對象的地址。
 若每次通過切片返回新對象(即第10行的操作),都會另外開辟空間,創建新對象,占用過多內存。
我們看以下代碼:
class Bunny:def __init__(self, name):self.name = nameself.__foods = []self.__weight = 7@propertydef name(self):return self.__name@name.setterdef name(self, value):self.__name = value@propertydef weight(self):return self.__weightb01 = Bunny("小白")print(b01.name, b01.weight)結果:
 
在代碼中,我們設置變量name為可讀可寫,而變量weight設置為只讀(即只可讀取不可以更改),如果我們試圖更改weight則會報錯。
發現問題
備注:我發現在可讀可寫的情況下(如變量name),在__init__()方法中,變量(name)前可以不加雙下劃線,即可以寫成【self.name = name】,而只讀情況下(如變量weight),在__init__()方法中,變量(weight)前要加雙下劃線,即可以寫成【self.__weight = 7】,若不加雙下劃線則會報錯【AttributeError: can’t set attribute】。這是為啥呢?
比如:
代碼1:
class Bunny:#__slots__ = ("__name", "__weight")def __init__(self, name):self.name = nameself.weight = 7@propertydef name(self):return self.__name@name.setterdef name(self, value):self.__name = value@propertydef weight(self):return self.__weightb01 = Bunny("小白") print(b01.name, b01.weight)結果1:
 
代碼2:
class Bunny:#__slots__ = ("__name", "__weight")def __init__(self, name):self.name = nameself.__weight = 7@propertydef name(self):return self.__name@name.setterdef name(self, value):self.__name = value@propertydef weight(self):return self.__weightb01 = Bunny("小白") print(b01.name, b01.weight)結果2:
 
代碼3:
class Bunny:#__slots__ = ("__name", "__weight")def __init__(self, name):self.name = nameself.weight = 7@propertydef name(self):return self.__name@name.setterdef name(self, value):self.__name = value@propertydef weight(self):return self.__weight@weight.setterdef weight(self, value):self.__weight = valueb01 = Bunny("小白") print(b01.name, b01.weight)結果3:
 
我們改一下weight試試:
代碼:
class Bunny:def __init__(self, name):self.name = nameself.__foods = []self.__weight = 7@propertydef name(self):return self.__name@name.setterdef name(self, value):self.__name = value@propertydef weight(self):return self.__weightb01 = Bunny("小白") b01.weight = 10結果:
 
 報錯了!
接下來,我們把foods設置為只讀,并試著更改一下foods:
class Bunny:def __init__(self, name):self.name = nameself.__foods = ["提草", "兔糧"]self.__weight = 7@propertydef name(self):return self.__name@name.setterdef name(self, value):self.__name = value@propertydef weight(self):return self.__weight@propertydef foods(self):return self.__foodsb01 = Bunny("小白") b01.foods = ["提草", "兔糧", "白菜"]結果:
 
 嗯!報錯了。
但在只讀情況下真的不可以更改么?我們看一下以下這段代碼:
class Bunny:def __init__(self, name):self.name = nameself.__foods = ["提草", "兔糧"]self.__weight = 7@propertydef name(self):return self.__name@name.setterdef name(self, value):self.__name = value@propertydef weight(self):return self.__weight@propertydef foods(self):return self.__foodsb01 = Bunny("小白") b01.foods.append("白菜") print(b01.name, b01.weight, b01.foods)結果:
驚!不僅沒有報錯,而且真的修改了b01.foods所關聯的列表。
我們畫一個簡易內存圖來解釋一下:
①當執行【b01 = Bunny("小白")】時:
 
②當執行【b01.foods.append("白菜")】時,【b01.foods】返回了【self.__foods】,【self.__foods】提供了可變對象的地址(foods關聯的是列表對象,列表是可變類型),通過地址,我們找到了列表對象,并對列表對象進行修改。但是在Bunny實例對象內部,存放的foods所關聯對象的地址沒有變,改變的只是列表對象本身,這種情況滿足只讀屬性的性質:
 
但是如果直接用【b01.foods = ["提草", "兔糧", "白菜"]】的方式對變量foods進行更改,則在內存中會創建新的列表對象,Bunny實例對象內部所存儲的foods所關聯對象的地址則會改變,則不滿足只讀屬性的性質。
但如果我們再更改一下代碼,返回foods列表的切片:
class Bunny:def __init__(self, name):self.name = nameself.__foods = ["提草", "兔糧"]self.__weight = 7@propertydef name(self):return self.__name@name.setterdef name(self, value):self.__name = value@propertydef weight(self):return self.__weight@propertydef foods(self):return self.__foods[:]b01 = Bunny("小白") b01.foods.append("白菜") print(b01.name, b01.weight, b01.foods)結果:
 
由結果可知,b01.foods并沒有被更改。這是為啥呢?
 這是因為我們在執行【b01.foods.append("白菜")】時,【b01.foods】返回的是【self.__foods[:]】,也就是說,返回了【self.__foods】的切片,即返回了一個新創建的列表對象的地址。則,我們通過這個新地址找到的新列表對象,并不是【self.__foods】所關聯的列表對象,所以就無法對b01.foods所關聯的原列表對象進行修改。
總結
以上是生活随笔為你收集整理的python面向对象(part2)--封装的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: python面向对象(part1)--类
- 下一篇: python面向对象(part3)--继
