1. 程式人生 > >day25-面向物件結構與成員

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
#金額不能為負