1. 程式人生 > >python 面向物件程式設計-基礎

python 面向物件程式設計-基礎

首先先來對比一下之前學習的內容:

  1. 面向過程:根據業務邏輯從上到下寫壘程式碼
  2. 函式式:將某功能程式碼封裝到函式中,日後便無需重複編寫,僅呼叫函式即可
  3. 面向物件:對函式進行分類和封裝,讓開發“更快更好更強...”
面向過程程式設計最易被初學者接受,其往往用一長段程式碼來實現指定功能,開發過程中最常見的操作就是貼上複製,即:將之前實現的程式碼塊複製到現需功能處,再來回憶一下當初學習時的監控報警的程式碼:
while Trueif cpu利用率 > 90%:
        #傳送郵件提醒
連線郵箱伺服器
        傳送郵件
        關閉連線
 
    if 硬碟使用空間 > 90
%: #傳送郵件提醒 連線郵箱伺服器 傳送郵件 關閉連線 if 記憶體佔用 > 80%: #傳送郵件提醒 連線郵箱伺服器 傳送郵件 關閉連線
開始使用了函數語言程式設計,增強程式碼的重用性和可讀性,就變成了這樣:
def 傳送郵件(內容)
    #傳送郵件提醒
連線郵箱伺服器
    傳送郵件
    關閉連線
 
while Trueif cpu利用率 > 90%:
        傳送郵件('CPU報警')
 
    if 硬碟使用空間 > 90%:
        傳送郵件('硬碟報警'
) if 記憶體佔用 > 80%: 傳送郵件('記憶體報警')
今天學習面向物件程式設計(Object Oriented Programming,OOP,面向物件程式設計),將會以什麼樣的方式呈現,請繼續向下看:

OOP是一種計算機程式設計架構。OOP的一條基本原則是計算機程式是由單個能夠起到子程式作用的單元或物件組合而成。OOP達到了軟體工程的三個主要目標:重用性、靈活性和擴充套件性。為了實現整體運算,每個物件都能夠接收資訊、處理資料和向其它物件傳送資訊。OOP主要有以下的概念和元件:

  元件資料和功能一起在執行著的計算機程式中形成的單元,元件在 OOP

計算機程式中是模組和結構化的基礎。

  抽象性程式有能力忽略正在處理中資訊的某些方面,即對資訊主要方面關注的能力。

  封裝也叫做資訊封裝:確保元件不會以不可預期的方式改變其它元件的內部狀態;只有在那些提供了內部狀態改變方法的元件中,才可以訪問其內部狀態。每類元件都提供了一個與其它元件聯絡的介面,並規定了其它元件進行呼叫的方法。

  多型性元件的引用和類集會涉及到其它許多不同型別的元件,而且引用元件所產生的結果得依據實際呼叫的型別。

  繼承性允許在現存的元件基礎上建立子類元件,這統一併增強了多型性和封裝性。典型地來說就是用類來對元件進行分組,而且還可以定義新類為現存的類的擴充套件,這樣就可以將類組織成樹形或網狀結構,這體現了動作的通用性。

另外,此程式設計方式的落地需要使用 “類” 和 “物件” 來實現,所以,面向物件程式設計其實就是對 “類” 和 “物件” 的使用。

那麼來看一下建立類:


例:

# 建立類
class Foo:
     
    def Bar(self):
        print 'Bar'
def Hello(self, name):
        print 'i am %s' %name
# 根據類Foo建立物件obj
obj = Foo()
obj.Bar()            #執行Bar方法
obj.Hello('zhang') #執行Hello方法
那麼他們真正執行下有什麼區別呢:

·        面向物件:【建立物件】【通過物件執行方法】

·        函式程式設計:【執行函式】

然後再細看一下OOP的這些特性:

一:封裝,簡單來說就是將內容封裝到某個地方,以後再去呼叫被封裝在某處的內容。

  #將內容封裝在某處。


self 是一個形式引數,當執行 obj1 = Foo('zhangsan', 18 ) 時,self 等於 obj1

                              當執行 obj2 = Foo('lisi', 28 ) 時,self 等於 obj2

所以,內容其實被封裝到了物件 obj1 和 obj2 中,每個物件中都有 name 和 age 屬性,在記憶體裡類似於下圖來儲存。


 #從某處呼叫被封裝的內容

  • 通過物件直接呼叫
  • 通過self間接呼叫
 首先看通過物件直接呼叫被封裝的內容
class Foo:
 
    def __init__(self, name, age):
        self.name = name
self.age = age
obj1 = Foo('zhangsan', 18)
print obj1.name    # 直接呼叫obj1物件的name屬性
print obj1.age     # 直接呼叫obj1物件的age屬性
obj2 = Foo('lisi', 73)
print obj2.name    # 直接呼叫obj2物件的name屬性
print obj2.age     # 直接呼叫obj2物件的age屬性 

  ##通過self間接呼叫被封裝的內容

class Foo:

    def __init__(self, name, age):
        self.name = name
self.age = age
def detail(self):
        print self.name
        print self.age

obj1 = Foo('zhangsan', 18)
obj1.detail()  # Python預設會將obj1傳給self引數,即:obj1.detail(obj1),所以,此時方法內部的 self = obj1,即:self.name 是 zhangsan ;self.age 是 18
obj2 = Foo('lisi', 73)
obj2.detail()  # Python預設會將obj2傳給self引數,即:obj1.detail(obj2),所以,此時方法內部的 self = obj2,即:self.name 是 lisi ; self.age 是 78
綜上所述,對於面向物件的封裝來說,其實就是使用構造方法將內容封裝到 物件 中,然後通過物件直接或者self間接獲取被封裝的內容。

二:繼承,面向物件中的繼承和現實生活中的繼承相同,即:子可以繼承父的內容。

例如:

  貓可以:喵喵叫、吃、喝、拉、撒

  狗可以:汪汪叫、吃、喝、拉、撒

如果我們要分別為貓和狗建立一個類,那麼就需要為 貓 和 狗 實現他們所有的功能,如下所示:

class def 喵喵叫(self):
        print '喵喵叫'
def (self):
        # do something
def (self):
        # do something
def (self):
        # do something
def (self):
        # do something
class def 汪汪叫(self):
        print '喵喵叫'
def (self):
        # do something
def (self):
        # do something
def (self):
        # do something
def (self):
        # do something


上述程式碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現:

  動物:吃、喝、拉、撒

     貓:喵喵叫(貓繼承動物的功能)

     狗:汪汪叫(狗繼承動物的功能)

class 動物:

    def (self):
        # do something
def (self):
        # do something
def (self):
        # do something
def (self):
        # do something
# 在類後面括號中寫入另外一個類名,表示當前類繼承另外一個類
class (動物):

    def 喵喵叫(self):
        print '喵喵叫'
# 在類後面括號中寫入另外一個類名,表示當前類繼承另外一個類
class (動物):

    def 汪汪叫(self):
        print '喵喵叫'
程式碼例項
class Animal:

    def eat(self):
        print "%s 吃 " %self.name

    def drink(self):
        print "%s 喝 " %self.name

    def shit(self):
        print "%s 拉 " %self.name

    def pee(self):
        print "%s 撒 " %self.name


class Cat(Animal):

    def __init__(self, name):
        self.name = name
self.breed = '貓'
def cry(self):
        print '喵喵叫'
class Dog(Animal):
    
    def __init__(self, name):
        self.name = name
self.breed = '狗'
def cry(self):
        print '汪汪叫'
# ######### 執行 #########
c1 = Cat('小白家的小黑貓')
c1.eat()

c2 = Cat('小黑的小白貓')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()
所以,對於面向物件的繼承來說,其實就是將多個類共有的方法提取到父類中,子類僅需繼承父類而不必一一實現每個方法。

學習了繼承的寫法之後,我們用程式碼來是上述阿貓阿狗的功能:

class Animal:

    def eat(self):
        print "%s 吃 " %self.name

    def drink(self):
        print "%s 喝 " %self.name

    def shit(self):
        print "%s 拉 " %self.name

    def pee(self):
        print "%s 撒 " %self.name


class Cat(Animal):

    def __init__(self, name):
        self.name = name
self.breed = '貓'
def cry(self):
        print '喵喵叫'
class Dog(Animal):
    
    def __init__(self, name):
        self.name = name
self.breed = '狗'
def cry(self):
        print '汪汪叫'
# ######### 執行 #########
c1 = Cat('小白家的小黑貓')
c1.eat()

c2 = Cat('小黑的小白貓')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()

那麼問題又來了,多繼承呢?

  • 是否可以繼承多個類
  • 如果繼承的多個類每個類中都定了相同的函式,那麼那一個會被使用呢?

1、Python的類可以繼承多個類,Java和C#中則只能繼承一個類

2、Python的類如果繼承了多個類,那麼其尋找方法的方式有兩種,分別是:深度優先廣度優先


  • 當類是經典類時,多繼承情況下,會按照深度優先方式查詢
  • 當類是新式類時,多繼承情況下,會按照廣度優先方式查詢

經典類和新式類,從字面上可以看出一個老一個新,新的必然包含了跟多的功能,也是之後推薦的寫法,從寫法上區分的話,如果 當前類或者父類繼承了object類,那麼該類便是新式類,否則便是經典類。



1:經典類多繼承

class D:

    def bar(self):
        print 'D.bar'
class C(D):

    def bar(self):
        print 'C.bar'
class B(D):

    def bar(self):
        print 'B.bar'
class A(B, C):

    def bar(self):
        print 'A.bar'
a = A()
# 執行bar方法時
# 首先去A類中查詢,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去D類中找,如果D類中麼有,則繼續去C類中找,如果還是未找到,則報錯
# 所以,查詢順序:A --> B --> D --> C
# 在上述查詢bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
a.bar()

2:新式類多繼承

class D(object):

    def bar(self):
        print 'D.bar'
class C(D):

    def bar(self):
        print 'C.bar'
class B(D):

    def bar(self):
        print 'B.bar'
class A(B, C):

    def bar(self):
        print 'A.bar'
a = A()
# 執行bar方法時
# 首先去A類中查詢,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去C類中找,如果C類中麼有,則繼續去D類中找,如果還是未找到,則報錯
# 所以,查詢順序:A --> B --> C --> D
# 在上述查詢bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
a.bar()

經典類:首先去A類中查詢,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去D類中找,如果D類中麼有,則繼續去C類中找,如果還是未找到,則報錯

新式類:首先去A類中查詢,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去C類中找,如果C類中麼有,則繼續去D類中找,如果還是未找到,則報錯

注意:在上述查詢過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了


三:多型

Pyhon不支援多型並且也用不到多型,多型的概念是應用於Java和C#這一類強型別語言中,而Python崇尚“鴨子型別”。

python 多型實現java和c#的多型

class F1:
    pass
class S1(F1):

    def show(self):
        print 'S1.show'
class S2(F1):

    def show(self):
        print 'S2.show'
# 由於在Java或C#中定義函式引數時,必須指定引數的型別
# 為了讓Func函式既可以執行S1物件的show方法,又可以執行S2物件的show方法,所以,定義了一個S1和S2類的父類
# 而實際傳入的引數是:S1物件和S2物件
def Func(F1 obj):
    """Func函式需要接收一個F1型別或者F1子類的型別"""
print obj.show()
    
s1_obj = S1()
Func(s1_obj) # 在Func函式中傳入S1類的物件 s1_obj,執行 S1 的show方法,結果:S1.show
s2_obj = S2()
Func(s2_obj) # 在Func函式中傳入Ss類的物件 ss_obj,執行 Ss 的show方法,結果:S2.show

python 的鴨子型別

class F1:
    pass
class S1(F1):

    def show(self):
        print 'S1.show'
class S2(F1):

    def show(self):
        print 'S2.show'
def Func(obj):
    print obj.show()

s1_obj = S1()
Func(s1_obj) 

s2_obj = S2()
Func(s2_obj)