1. 程式人生 > >python面向對象OOP入門

python面向對象OOP入門

python面向對象特性之類的三大特性

一、面向對象簡介

面向對象編程不是python獨有,幾乎所有高級語言都支持;面向對象不管在那個語言中都有三大特性:即:封裝、繼承、多態;具體的本文主要講python面向對象--類及三大特性的具體實現;



二、面向對象之python類特性一:封裝

python通過類實現面向對象的編程;編程大致分為面向過程式的函數式編程,和面向對象編程;

類(Class)和實例(Instance)是面向對象最重要的概念。

1、類的簡單定義與使用

class ‘類名‘:
    語句塊

如:

class Foo:
    def bar(self):
        print("Foo.bar")
        
        
obj = Foo()   #obj即是Foo類的實例
obj.bar()     #通過實例去調用調用Foo類中的方法

結果輸出Foo.bar

以上就是最簡單的類定義與使用;

2、self是什麽?

我們註意到以上的示例中除了class關鍵字用來定義類,def定義方法(類中的函數我們稱方法,可以理解為類中封裝的函數就是方法)後面有一個self參數,那麽 這個self參數是什麽?先來一個例子:

class Foo:
    def bar(self,arg):
         print(self,arg)
         
z1 = Foo()
print(z1)
z1.bar(11)

輸出結果:

技術分享圖片

以上結果可以看到z1是Foo實例打印出內存地址和self的內存地址一樣,這說明self就代指實例本身;

因此self就是類實例化出來的對象(實例 ),通俗的講,就是那個實例對象調用,就代指那個實例對象


3、實例出對象

實例對象是從類實例化而來obj = 類名([參數....]),擁有自己的作用域命名空間,就是擁有自己的內存地址空間;同時可以對實例對象添加賦值;用於保存實例自己的數據;同時可以調用類中封裝好的方法或數據;

class Foo:
    def bar(self,arg):
        print(self.name,self.age,arg)

obj = Foo()      #實例化obj對象
obj.name = "san"   #向實例中添加對象name並賦值
obj.age = 18        #向實例中添加對象age並賦值
obj.bar(666)        #調用類方法並傳入參數6666

輸出結果:

(‘san‘, ‘18‘, 666)

從上面的示例可以看出類在定義時並沒有創建name 和age ;obj實例對象創建後通過賦值

讓obj擁有了name和age;因此實例obj在調用類中bar方法時可以獲取自身命名空間中name

和age值;實際上實例化後的對象如果沒有做出限制(__slots__),可以向對象中保存添加任意多的值;


4、通過__slots__限制姨實例對象添加賦值

通過以上示例我們了解到,在從類實例化對象後,可以往對象中添加任意多的值,只要內存足夠大;然而任何無節制的增加,都有可能帶來隱患,所以我們通過__slots__功能來限制實例對象的賦值;

class Foo:
    __slots__ = ("name","age")    #只允許往實例賦name,age
    def bar(self,arg):
        print(self.name,self.age,arg)
    def Other(self):      
        print(self.other)
  
z = Foo()          #實例化z
z.name = "san"     #向z對象賦值name
z.age = 18         #向z對象賦值age
z.bar("6666")
z.other = "othter"   #向z對象賦值other
z.Other()

運行結果如圖:

技術分享圖片

這次沒有賦值成功,被限制了提示:“AttributeError: ‘Foo‘ object has no attribute ‘other‘“;這正是我們所要的。


5、構造函數__init__

對於以上的示例我們往實例化後的對象中賦值也可以限制能賦的值,如果要實例化出多個類對象,每個類對象有共同的屬性值,如name,age,sex等,那麽我們可以通過構造函數__init__方法來實現,即在實例化創建對象時傳入name,age,來達到類似obj.name ="san" obj.age = 18這樣的效果:

class Foo:
    def __init__(self,name,age):   #構造方法,這裏有幾個參數,實例化對象時就要傳遞對應的參數
        self.name = name
        self.age = age
        print("__init__ is start.")   #對象實例化時自動執行
    def foo(self,arg):
        print(self.name,self.age,arg)
        
obj = Foo("san",18)
obj.bar("good")

運行結果如圖:

技術分享圖片

從運行結果中可以看出 只要實例化出對象obj = Foo(["參數...."])時就執行__init__方法中的內容;利用這個功能,我們給各實例傳入name,age參數時自動賦值;這就是構造方法的功能;其中參數可選,有無參數在於你設計類時是否需要。


三、類特性二:繼承

繼承民是面向對象的特性之一,即子類(又叫派生類)繼承父類(又叫基類或超類)的特性(屬性,方法等);同時子類可以重寫父類中的方法或屬性。說到繼承這時提一嘴,python2.x和python3.x繼承有點區別,python2.x是經典類,通過class 類名(object)變成新式類,python3.x默認是新式類;新式類和經典類的區別在於,多繼承時,繼承順序不一樣;經典類采用”深度優先“方式 查找;而新式類則是廣度優先去查找;這裏就不詳述,以下的示例在python 2.7中所以你會看到類名後有一個object,python3.x下可以不用object。關於深度優先和廣度優先這裏先不詳述,下面有示例:


1、簡單繼承

示例:

class F(object):           #父類F
    def f1(self):
            print("F.f1")
    def f2(self):        
            print("F.f2")
class S(F):                #子類S
    def s1(self):
            print("s.s1")
            
son = S()    #從S類實例化對象son
son.s1()     #調用S類的s1方法
son.f1()     #由於在S類中沒有找到f2,所以到父類F中找到f1

運行結果如圖:

技術分享圖片

說明:雖然S類中沒有f2方法,但由於S類繼承了F類,而F類中有f1方法,因此可以調用F中的f1方法。


2、重寫

有時候我們在繼承了父類後,父類中的方法可能並不能滿足需求,直接修改父類中的方法又破壞了原類,可能影響到其他引用,此時可以通過在子類中定義一個父類中同名的方法,就可以達到重寫父類方法的目的。

示例:

class F(object):
    def f1(self):
        print("F.f1")
    def f2(self):
        print("F.f2")
class S(F):
    def s1(self):
        print("s.s1")
    def f2(self):  # 不繼承,重寫
        print("S.f2")
son = S()
son.s1()  #S類的s1方法
son.f1()  #繼承F類的f1方法
son.f2()  #S自己的f2方法,重寫(覆蓋)F類的f2方法

運行結果:

技術分享圖片

3、子類中調用超類方法

雖然上面的例子中重寫了父類中的同名方法,滿足了需求,但能否在重寫的基礎上,引用超類中的方法呢?即先運行超類中的同名方法,再定義自己的同名方法?答案是必須可以啊!

這裏有兩種方法,一起列舉,現實中只用其中的一種,建議使用super:

class F(object):
    def f1(self):
        print("F.f1")
    def f2(self):
        print("F.f2")
class S(F):
    def s1(self):
        print("s.s1")
    def f2(self):  # 不繼承,重寫
        print("S.f2")
        super(S,self).f2()    #調用父類f2方法一: super(子類,self).父類中的方法(...)
        F.f2(self)            #調用父類f2方法二:父類.方法(self,....)
        
son = S()
son.s1()  #S類的s1方法
son.f1()  #繼承F類的f1方法
son.f2()  #S自己的f2方法,重寫(覆蓋)F類的f2方法

運行結果:

技術分享圖片

由於S類中的f2方法除了輸出自己的功能S.f2外還通過兩種調用父類F中的f2方法,所以輸出了兩次F.f2;

註意這裏為了演示只調用了父類中的f2方法,達到繼承和重寫優化父類中的f2方法,也可以通過這兩種方法中的一種調用父類中的其他方法;


4、類多繼承

類可以繼承多個類,即在class 類名(父類1,父類2,...)那麽問題來,如果所繼承的類都有一個同名的方法,那調用時如何選擇?上面提到了經典類是深度優先,新式類時廣度優先,本文不做深度優先和廣度優先查找比較,只講新式類的廣度優先;有興趣的可以自行查找資料。

示例:

class Base(object):
    def a(self):
        print("Base.a")
class F1(Base):
    def a1(self):
        print(‘F1.a‘)
    def a11(self):
        print("F1.a11")
        
class F2(Base):
    def a2(self):
        print(‘F2.a‘)
    def a11(self):
        print("F2.a11")
class S(F1,F2):
    pass
class S2(F2,F1):
    pass
obj = S()
obj.a11()
obj2 = S2()
obj2.a11()

運行結果:

技術分享圖片

obj繼承順序是F1,F2 結果是F1.a11

obj2繼承順序是F2,F1 結果是F2.all


5、有交集的多繼承查找


obj對象從A類實例化而來,A類繼承了B和C類,B類繼承D,C類也繼承D類;baba方法在C類和D類中都有,此時obj.baba()方法結果是怎樣?

示例:

class D:
    def bar(self):
        print(‘D.bar‘)
    def baba(self):
        print("D.baba")
class C(D):
    def bar(self):
        print(‘C.bar‘)
    def baba(self):
        print("C.baba")
class B(D):
    def bar(self):
        print(‘B.bar‘)
class A(B, C):
    def bar(self):
        print(‘A.bar‘)

obj = A()
obj.bar()
obj.baba()

運行結果:

技術分享圖片


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


同樣的例子我們調回python2.7作為經典類執行,看結果。

技術分享圖片

執行bar和baba方法時
首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麽有,則繼續去D類中找,如果D類中麽有,則繼續去C類中找,如果還是未找到,則報錯
所以,查找順序:A --> B --> D --> C
在上述查找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了

這裏的baba方法在D中找到,所以顯示為D.baba

深度優先和廣度優先查找順序如圖:

技術分享圖片


6、多繼承之__init__執行順序

以下是一個模擬實踐中多繼承情況下__init__構造方法的執行,有利於我們閱讀項目源碼。

示例:

class BaseRequest:    #(3)
   def __init__(self):
       print("BaseRequest.init")
class RequestHandler(BaseRequest):  #(2)
    def __init__(self):
        print("RequestHandler.init")
        BaseRequest.__init__(self)   #調用父類的__init__方法
    #     super(RequestHandler,self).__init__()

    def serve_forever(self):     #(5)
        print("RequestHandler.serve_forever")
        self.process_request()

    def process_request(self):
        print(‘RequestHandler.process_request‘)
class Minx:
    def process_request(self):   #(6)
        print("Minx.process_request")

class son(Minx,RequestHandler):
    pass

obj = son()     #(1)
obj.serve_forever()    #(4)

以上的註釋後的數字是執行的流程。obj = son()的作用:

實例化出obj對象;

執行__init__構造方法,在多繼承環境 下,和上面講的調用其他類方面查找一致,首先查找 son類中的__init__如果沒有按新式類的方度優先順序查找所繼承類中的__init__,這裏首先找到Minx,沒有,再找到RequestHandler類,執行打印“RequestHandler.init”,裏的__init__再次執行了父類中的__init__方法,因此又打印出"BaseRequest.init";

執行obj.serve_forever()時,同樣的查找,打印出“RequestHandler.serve_forever”,註意這裏又調用 了self.process_request()方法,由於self指的就是所調用的對象obj即obj.process_request()方法,因此重新從son(Minx,RequestHandler)中按順序查找,而不是RequestHandler類下的 process_reques方法。因此找到Minx下的process_request方法,得到結果:“Minx.process_request”


執行結果如下:

技術分享圖片



四、python面向對像之多態

多態簡單的理解就是同一個方法在不現的對象調用時顯示出的效果不一樣,如+ 在整形數字相加時是數學運算,在字符串相加時加是連接符;

python的面向對象原生支持多態,不像java等強類型語言,傳遞參數時必須指定類型,而python沒有此此限制,這也是python原生動支持多態的原因之一。



本文主要說明了python面向對象的三大特性,封裝,繼承及多態,繼承有多繼承,新式繼承與經典類繼承的區別。個人總結,如有不當之處歡迎指正。



本文出自 “學無止境,學以致用” 博客,請務必保留此出處http://dyc2005.blog.51cto.com/270872/1982808

python面向對象OOP入門