1. 程式人生 > >python快速學習系列(6):面向物件程式設計(OOP)

python快速學習系列(6):面向物件程式設計(OOP)

一、面向物件程式設計:

1.比設計模式更重要的是設計原則:
1)面向物件設計的目標:
·可擴充套件:新特性很容易新增到現有系統中,基本不影響系統原有功能
·可修改:當修改某一部分程式碼時,不會影響到其他不相關的部分
·可替代:用具有相同介面的程式碼去替換系統中某一部分程式碼時,系統不受影響
2)面向物件設計的SOLID原則:
·單一職責原則:設計出來的每一個類,只有一個引起這個類變化的原因
·開閉原則:對擴充套件開放,對修改封閉
·替換原則:父類能用的功能,換成子類一樣可以用
·介面隔離原則:介面設計要按需供給(類似微伺服器設計)
·依賴倒置原則:抽象不依賴於細節,細節應該依賴於抽象(針對介面程式設計)
3)AI(場景)+python(語言)+OOP(程式設計模式)
·AI:業務導向不明顯,需求變動頻率較低,實現和復現頻率高
·python:雖然是一門面向物件語言,但與傳統的面向物件並不相同
·OOP:使用python時,並不需要深入學習OOP或OOD那些理論

二、OOP in python

1.類的建立、資料繫結

class Model:
    __name = 'DNN'
    def __init__(self,name):
        self.__name = name
    def print_name(self):
        print(self.__name)
    @classmethod        #裝飾器,表示下面這個函式是類所有的
    def print_cls_name(cls):#這裡的引數名一般使用cls(同之前的函式都使用self一樣)
        print(cls.__name)


class CNNModel(Model):
    __name = 'CNN'
    def __init__(self,name,layer_num):
        Model.__init__(self,name)#子類的初始化函式必須顯式的包含父類的初始化函式
        self.__layer_num = layer_num

    def print_layer_num(self):
        print(self.__layer_num)

def main():
    cnnmodel = Model('CNN')   #例項化
    rnnmodel = Model('RNN')
    print(cnnmodel)  #例項化完的物件可以直接print
    print(rnnmodel.__name)  #用.訪問成員,報錯,因為是私有成員,不能類外訪問
    cnnmodel.print_name()

要點:
·類名一般大寫,例項化出來的物件,名稱一般小寫
·類在被定義時,建立了一個區域性作用域;類例項化出物件後,建立了新的區域性作用域,額就是物件自己的作用域
·類名加上()生成物件,說明類被定義後便可以呼叫
·本質上講,類本身是一個物件
·在類定義中,self只帶例項化出來的物件
·cnnmodel.print_name()等價於Model.print_name(cnnmodel)
·通過雙下劃線開頭,可以將資料屬性私有化,對於方法一樣適用(資料在外部不能訪問,但是可以通過函式訪問)
·python中的私有化是假的,本質上做了一次名稱替換,因此實際中也有為了方便除錯而使用單下劃線的情況,而私有化也全憑自覺了
·使用了@classmethod後的方法雖然可以繼承,但是方法裡面的cls引數綁定了父類,即使在子類中呼叫了類方法,但通過cls引用的屬性依舊是父類的類屬性

三、 Pythonic OOP

1.關於雙下劃線
1)怎麼讀?
_name:single underscore name
__name:double underscore name
name:dunder name(dunder來源:double首字母d,underscore取under)
init():dunder init method (function)
2)雙下劃綫開頭和結尾的變數或方法叫什麼?
·類別:special;magic;dunder
·實體:attribute,method
3)如何認識python的special method
·special method:method with special name(dunder)
·Why use it?A class can implement certain operation that are invoked by special syntax
·original intention of design:operation overloading

2.Pythonic OOP with Special Method and Attribute
·每一個class在定義的時候如果沒有繼承,都會隱式繼承object這個superclass(超類)
·每一個自定義的class在Python中都是一個type object

class X:
    pass 
class Y(x):
    pass
def main():
    x = X()
    y = Y()
    print(x.__class__.__name__)  #X
    print(y.__class__.__name__)  #Y
    print(X.__class__.__name__)  #type
    print(Y.__class__.__name__)  #type
    print(x.__class__.__base__.__name__)  #object
    print(y.__class__.__base__.__name__)  #X
    print(X.__class__.__base__.__name__)  #object
    print(Y.__class__.__base__.__name__)  #object

3.Attribute Access and Peoperties
Attribute相關的操作一般有:
·create
·read
·Update
·Delete

1)level 1 basic access(default access)
class X:
pass
if name == ‘main’:
X.a = ‘a’
print(X.a)
X.a = ‘aa’
print(X.aa)
del X.a
上述例子說明即使在類中沒有繫結資料,外部依然可以繫結(太逆天了)
但是如果例項化的物件中沒有這個Attribute,訪問時會報錯

2)level 2:Property(長得像Attribute的Method) ——推薦使用
Property的設計初衷:
·程式碼複用
·延遲計算
·更加規範的物件屬性訪問管理

場景:我要減肥,需要監控BMI指標,但是隻能測量體重,每天更新體重,隔幾天看一次BMI指數

class X:
    def __init__(self,w,h):
        self.w = w
        self.h = h
        self.BMI = w / h ** 2
def main():
    x = X(175,1.83)
    print(x.BMI)    #22.3954
    x.w = 74
    x.w = 73
    x.w = 72
    print(x.BMI)    #22.3954

注意:上述雖然把w傳入了,但是__init__()函式只在初始化的時候執行一次,後續不再執行

改進一:

class X:
    def __init__(self,w,h):
        self.__w = w
        self.__h = h
        self.BMI = w / h ** 2
    def update_w(self,w):
        self.__w = w
        self._update_bmi()
    def _update_bmi(self):
        self.BMI = self.__w / self.__h ** 2

def main():
    x = X(75,1.83)
    print(x.BMI)  #22.3954
    x.update_w(74)
    x.update_w(73)
    x.update_w(72)
    print(x.BMI)  #21.4996

缺點:
BMI屬性依舊可以被外部訪問和修改
無論BMI屬性是否被訪問,每次w更新均更新BMI,造成一定的計算資源浪費

改進二:

class X:
    def __init__(self,w,h):
        self.w = w
        self.h = h
    def get_bmi(self):
        return self.w / self.h ** 2
def main():
    x = X(75,1.83)
    print(x.get_bmi())
    x.w = 74
    x.w = 73
    x.w = 72
    print(x.get_bmi())

缺點:
·上述程式碼依然需要更改main函式中的語句
·當w變化頻率小於BMI訪問次數時,需要進行大量的重複計算

改進三:

class X:
    def __init__(self,w,h):
        self.__w = w
        self.__h = h
        self.__bmi = w / h ** 2
    def get_w(self):
        return self.__w
    def set_w(self,value):
        if value <= 0 :
            raise ValueError('weight below 0 is not possible')
        self.__w = value
        self.__bmi = self.__w / self.__h ** 2
    def get_bmi(self):
        return self.__bmi
    w = property(get_w,set_w)  #***括號裡面的引數必須是函式名
    BMI = property(get_bmi)    #***非常重要
def main():
    x = X(75,1.83)
    print(x.BMI)
    x.w = 74
    x.w = 73
    x.w = 72
    print(x.BMI)

分析:
·通過Property物件顯式的控制屬性的訪問
·僅在w被更改的時候更新BMI,充分避免了重複計算
·很容易的增加了異常處理,對更新屬性進行預檢驗
·完美複用原始呼叫程式碼,在呼叫方不知情的情況完成功能新增
實際使用中並不會這樣寫,有更加優美的寫法

改進四:

class X:
    def __init__(self,w,h):
        self.__w = w
        self.__h = h
        self.__bmi = w / h ** 2
    @property   #只讀
    def w(self):
        return self.__w
    @w.setter   #可寫
    def w(self,value):
        if value <= 0 :
            raise ValueError('weight below 0 is not possible')
        self.__w = value
        self.__bmi = self.__w / self.__h ** 23
    @property   #只讀
    def BMI(self):
        return self.__bmi

def main():
    x = X(75,1.83)
    print(x.BMI)
    x.w = 74
    x.w = 73
    x.w = 72
    print(x.BMI)

關於property的用法:
·property(fget=None,fset=None,fdel=None,doc=None)
·使用@property預設實現了可讀
·被@property裝飾過的method可以通過@method.setter繼續裝飾單輸入引數方法實現可寫

3.Cross-Cutting and Duck Typing
·單繼承vs多型
-單繼承保證了縱向的複用和一致性
-多型保證了跨型別的複用和一致性

·傳統OOP vs 鴨子型別
-傳統OOP基於類別進行設計,從類別出發逐步擴充套件
-鴨子型別進考慮功能,從需要滿足的功能出發進行設計

·傳統的OOP的多型 vs 鴨子型別的多型
-傳統OOP中的多型大多基於共同的基類進行設計
-python中的鴨子型別無需考慮繼承關係,實現了某個通用的介面就可以完成多型設計(special method)

Cross-Cutting:基於鴨子型別的視角看Decorator與special method
·通過一個類實現一個個的special method,你就讓這個類越來越像python的Built-in class
·實現special method是從語言銜接層面為你的class賦能
·實現decorator是從通用的函式功能層面為你的class賦能
·通過multi-Inheritance,利用MixIn的理念,你可以為你的class批量化的賦能