day25-面向物件結構與成員
1、面向物件結構分析
如下面的圖所示:面向物件整體大致分兩塊區域:
每個大區域又可以分為多個小部分:
class A: name = 'Tom' # 靜態變數(靜態欄位) __iphone = '138xxxxxxxx' # 私有靜態變數(私有靜態欄位) def __init__(self,name,age): #特殊方法 self.name = name #物件屬性(普通欄位) self.__age = age #私有物件屬性(私有普通欄位) def func1(self): #普通方法pass def __func(self): #私有方法 print(666) @classmethod # 類方法 def class_func(cls): """ 定義類方法,至少有一個cls引數 """ print('類方法') @staticmethod #靜態方法 def static_func(): """ 定義靜態方法 ,無預設引數""" print('靜態方法') @property #屬性 def prop(self):pass
類有這麼多的成員,那麼我們先從那些地方研究呢? 可以從私有與公有部分和方法的詳細分類兩個方向去研究。
2、面向物件的私有與公有
對於每一個類的成員而言都有兩種形式:
公有成員,在任何地方都能訪問
私有成員,只有在類的內部才能方法
私有成員和公有成員的訪問限制不同:
2.1、靜態欄位(靜態變數)
公有靜態欄位:類可以訪問;類內部可以訪問;派生類中可以訪問
私有靜態欄位:僅類內部可以訪問;
1)公有靜態欄位
class C: name = "公有靜態欄位" def func(self): print C.name #從類內部訪問類的公有屬性class D(C): def show(self): print C.name #從派生類訪問父類的公有屬性 C.name # 類訪問 obj = C() obj.func() # 類內部可以訪問 obj_son = D() obj_son.show() # 派生類中可以訪問
2)私有靜態欄位
class C: __name = "私有靜態欄位" def func(self): print C.__name #可以在類內部訪問私有屬性 class D(C): def show(self): print C.__name #不可以在子類中訪問父類的私有屬性 C.__name # 不可在外部訪問 obj = C() obj.__name # 不可在外部訪問 obj.func() # 類內部可以訪問 obj_son = D() obj_son.show() #不可在派生類中可以訪問
2.2、普通欄位(物件屬性)
公有普通欄位:物件可以訪問;類內部可以訪問;派生類中可以訪問
私有普通欄位:僅類內部可以訪問
1)公有普通欄位
class C: def __init__(self): self.foo = "公有欄位" def func(self): print(self.foo) #類內部可以訪問 class D(C): def show(self): print(self.foo) #派生類中可以訪問 obj = C() obj.foo # 通過物件訪問 obj.func() # 類內部訪問 obj_son = D(); obj_son.show() # 派生類中訪問
2)私有普通欄位
class C: def __init__(self): self.__foo = "私有欄位" def func(self): print self.__foo # 類內部可以訪問 class D(C): def show(self): print self.foo # 派生類中不可以訪問 obj = C() obj.__foo # 通過物件訪問 ==> 錯誤 obj.func() # 類內部訪問 ==> 正確 obj_son = D(); obj_son.show() # 派生類中訪問 ==> 錯誤
2.3、方法
公有方法:物件可以訪問;類內部可以訪問;派生類中可以訪問
私有方法:僅類內部可以訪問
1)公有方法
class C: def __init__(self): pass def add(self): print('in C') class D(C): def show(self): print('in D') def func(self): self.show() obj = D() obj.show() # 通過物件訪問 obj.func() # 類內部訪問 obj.add() # 派生類中訪問
2)私有方法
class C: def __init__(self): pass def __add(self): print('in C') class D(C): def __show(self): print('in D') def func(self): self.__show() obj = D() obj.__show() # 通過不能物件訪問 obj.func() # 類內部可以訪問 obj.__add() # 派生類中不能訪問
2.4、總結
對於這些私有成員來說,他們只能在類的內部使用,不能在類的外部以及派生類中使用.
ps:非要訪問私有成員的話,可以通過 物件._類__屬性名obj._Classname__privateAttributeOrMethod 來訪問:
,但是絕對不允許!!!
為什麼可以通過._類__私有成員名訪問呢?因為類在建立時,如果遇到了私有成員(包括私有靜態欄位,私有普通欄位,私有方法),它會將其儲存在記憶體時自動在前面加上_類名,可以在__dict__中看到。
3、面向物件的成員
3.1、欄位
欄位包括:普通欄位和靜態欄位,他們在定義和使用中有所區別,而最本質的區別是記憶體中儲存的位置不同,
普通欄位屬於物件
靜態欄位屬於類
class Province: # 靜態欄位 country = '中國' def __init__(self, name): # 普通欄位 self.name = name # 直接訪問普通欄位 obj = Province('河北省') print obj.name # 直接訪問靜態欄位 Province.country
上述程式碼可以看出:
普通欄位需要通過物件來訪問,靜態欄位通過類訪問
在使用上可以看出普通欄位和靜態欄位的歸屬是不同的
其在記憶體的儲存方式類似如下圖:
由上圖可以看出:
靜態欄位在記憶體中只儲存一份
普通欄位在每個物件中都要儲存一份
應用場景:
通過類建立物件時,如果每個物件都具有相同的欄位,那麼就使用靜態欄位
3.2、方法
方法包括:普通方法、靜態方法和類方法,三種方法在記憶體中都歸屬於類,區別在於呼叫方式不同。
普通方法,也叫例項方法
定義:第一個引數必須是例項物件,該引數名一般約定為“self”,通過它來傳遞例項的屬性和方法(也可以傳類的屬性和方法);
呼叫:只能由例項物件呼叫。
類方法
定義:使用裝飾器@classmethod。第一個引數必須是當前類物件,該引數名一般約定為“cls”,通過它來傳遞類的屬性和方法(不能傳例項的屬性和方法);
呼叫:例項物件和類物件都可以呼叫。
靜態方法
定義:使用裝飾器@staticmethod。引數隨意,沒有“self”和“cls”引數,但是方法體中不能使用類或例項的任何屬性和方法;
呼叫:例項物件和類物件都可以呼叫。
例項方法就是第一個引數是self,類方法就是第一個引數是cls,而靜態方法不需要額外的引數
類方法和靜態方法都可以訪問類的靜態變數(類變數),但不能訪問例項變數,例如不能訪問self.name,而普通方法則可以
1)普通方法
簡而言之,例項方法就是類的例項能夠使用的方法。這裡不做過多解釋。
2)類方法
使用裝飾器@classmethod。
原則上,類方法是將類本身作為物件進行操作的方法。假設有個方法,且這個方法在邏輯上採用類本身作為物件來呼叫更合理,那麼這個方法就可以定義為類方法。另外,如果需要繼承,也可以定義為類方法。
如下場景:
假設我有一個學生類和一個班級類,想要實現的功能為:
執行班級人數增加的操作、獲得班級的總人數;
學生類繼承自班級類,每例項化一個學生,班級人數都能增加;
最後,我想定義一些學生,獲得班級中的總人數。
思考:這個問題用類方法做比較合適,為什麼?因為我例項化的是學生,但是如果我從學生這一個例項中獲得班級總人數,在邏輯上顯然是不合理的。同時,如果想要獲得班級總人數,如果生成一個班級的例項也是沒有必要的。
class ClassTest: __num = 0 @classmethod def addNum(cls): cls.__num += 1 @classmethod def getNum(cls): return cls.__num # 這裡我用到魔術函式__new__,主要是為了在建立例項的時候呼叫人數累加的函式。 def __new__(cls, *args, **kwargs): Class.add_num() #括號裡不用寫cls return object.__new__(cls) class Student(Class): def __init__(self,name): self.name = name s1 = Student('小王') s1 = Student('小張') print(Class.get_num()) 結果:2
3)靜態方法
使用裝飾器@staticmethod。
靜態方法是類中的函式,不需要例項。靜態方法主要是用來存放邏輯性的程式碼,邏輯上屬於類,但是和類本身沒有關係,也就是說在靜態方法中,不會涉及到類中的屬性和方法的操作。可以理解為,靜態方法是個獨立的、單純的函式,它僅僅託管於某個類的名稱空間中,便於使用和維護。
譬如,我想定義一個關於時間操作的類,其中有一個獲取當前時間的函式。
import time class TimeTest: def __init__(self, hour, minute, second): self.hour = hour self.minute = minute self.second = second @staticmethod def showTime(): return time.strftime("%H:%M:%S", time.localtime()) print(TimeTest.showTime()) t = TimeTest(2, 10, 10) nowTime = t.showTime() print(nowTime)
如上,使用了靜態方法(函式),然而方法體中並沒使用(也不能使用)類或例項的屬性(或方法)。若要獲得當前時間的字串時,並不一定需要例項化物件,此時對於靜態方法而言,所在類更像是一種名稱空間。
其實,我們也可以在類外面寫一個同樣的函式來做這些事,但是這樣做就打亂了邏輯關係,也會導致以後程式碼維護困難。
3.3、屬性
在Python中,property可以將方法變成一個屬性來使用,藉助property可以實行Python風格的getter/setter,即可以通過property獲得和修改物件的某一個屬性。
例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解)
成人的BMI數值:
過輕:低於18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高於32
體質指數(BMI)=體重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
#不使用property class BMI: def __init__(self, name, weight, high): self.name = name self.high = high self.weight = weight def bmi_show(self): rst = self.weight/self.high ** 2 print('%s的BMI值為%s' %(self.name, rst)) p1 = BMI('Tom', 65, 1.7) p1.bmi_show() #需要通過呼叫方法()的方式顯示結果 #使用property class BMI: def __init__(self, name, weight, high): self.name = name self.high = high self.weight = weight @property def bmi_show(self): rst = self.weight/self.high ** 2 print('%s的BMI值為%s' %(self.name, rst)) p1 = BMI('Tom', 65, 1.7) p1.bmi_show #將方法偽裝成一個屬性,通過呼叫屬性的方式顯示結果
為什麼要用property
將一個類的函式定義成特性以後,物件再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函式然後計算出來的,這種特性的使用方式遵循了統一訪問的原則
@property可以將python定義的函式“當做”屬性訪問,從而提供更加友好訪問方式。
1》只有@property表示只讀。
2》同時有@property和@x.setter表示可讀可寫。
3》同時有@property和@x.setter和@x.deleter表示可讀可寫可刪除。
由於新式類中具有三種訪問方式,我們可以根據他們幾個屬性的訪問特點,分別將三個方法定義為對同一個屬性:獲取、修改、刪除
class A: @property def func(self): print('get的時候執行我') @func.setter def func(self,value): print('set的時候執行我') @func.deleter def func(self): print('del的時候執行我') #只有在方法func定義property後才能定義func.setter,func.deleter obj1 = A() obj1.func #get的時候執行我 obj1.func = 'a' #set的時候執行我 del obj1.func #del的時候執行我 或者: class Foo: def get_func(self): print('get的時候執行我啊') def set_func(self,value): print('set的時候執行我啊') def delete_func(self): print('delete的時候執行我啊') func=property(get_func,set_func,delete_func) #內建property三個引數與get,set,delete一一對應 obj = Foo() obj.func #get的時候執行我啊 obj.func = 'a' #set的時候執行我啊 del obj.func # delete的時候執行我啊
例子:顯示蘋果的價格
class Apple: def __init__(self, ori_price, discount): self.ori_price = ori_price self.discount = discount @property def price(self): ret = self.ori_price * self.discount print('蘋果的折扣價是%s'%ret) @price.setter def price(self, new_price): self.ori_price = new_price apple1 = Apple(8, 0.8) #定義蘋果的價格是8元/kg,折扣是0.8 apple1.price apple1.price = 7 #重新定義蘋果的價格是7元/kg,這時蘋果的價格是7*0.8 apple1.price
銀行賬號的例子,我們要確保沒人能設定金額為負,並且有個只讀屬性cny返回換算人名幣後的金額。
class Account: def __init__(self, rate): self._amount = 0 self.rate = rate @property def amount(self): """賬號餘額(美元)""" return self._amount @property def cny(self): """賬號餘額(人名幣)""" return self._amount * self.rate @amount.setter def amount(self, value): if value < 0: print('金額不能為負') else: self._amount = value obj1 = Account(6.6) obj1.amount = 100 print(obj1.cny) #660.0 obj1.amount = -100 #金額不能為負