1. 程式人生 > >Python基礎之(面向對象初識)

Python基礎之(面向對象初識)

提示 利用 字典 全部 定義 可能 優點 del 情況

一、初識面向對象

1.1、面向過程的程序的核心是過程(流水線式思維),過程即解決問題的步驟,面向過程的設計就好比精心設計好一條流水線,考慮周全什麽時候處理什麽東西。

優點是:極大的降低了寫程序的復雜度,只需要順著要執行的步驟,堆疊代碼即可

缺點是:一套流水線或者流程就是用來解決一個問題,代碼牽一發而動全身,擴展性較差

1.2、面向對象的程序的核心是對象(上帝式思維),要理解對象為何物,必須把自己當成上帝,上帝眼裏世間存在的萬物皆為對象,不存在的也可以創造出來。被上帝造出來的每個人都有各自的特征和技能(這就是對象的概念,特征和技能分別對應對象的屬性和方法)

優點是:解決了程序的擴展性。對某一個對象單獨修改,會立刻反映到整個體系中,如對遊戲中一個人物參數的特征和技能修改都很容易。

缺點:可控性差,無法向面向過程的程序設計流水線式的可以很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題,即便是上帝也無法預測最終結果

面向對象的程序設計並不是全部。對於一個軟件質量來說,面向對象編程可以使程序的維護和擴展變得更簡單,並且可以大大提高程序開發效率 ,另外、基於面向對象的程序可以使他人更加容易理解你的代碼邏輯,從而使團隊開發變得更從容

1.3、類與對象

類即類別、種類,是面向對象設計最重要的概念,對象是特征與技能的結合體,而類則是一系列對象相似的特征與技能的結合體

在現實世界中:先有對象,再有類

在程序中:務必保證先定義類,後產生對象

實例化即是由類產生對象的過程,實例化的結果就是一個類,或者叫一個實例

二、類的基礎知識

2.1、創建一個類

‘‘‘
class 類名:
    ‘類的文檔字符串‘
    類體
‘‘‘
#與創建一個函數相似

class Person:  #創建一個類
    pass

#python2中分經典類與新式類,python3中都是新式類
#經典類
class Person:
    pass

#新式類
class Person(父類名稱):
    pass

#申明一個新式類:
class Person(object):
    pass

2.2、屬性方法

class Person:
    "這是個人的類"
    role = "person"   #數據屬性
    
    def type(self):     #函數屬性(類中的方法)
        print("yellow race") 


print(Person.role)  #調用屬性
Person.type(Person1)  #要傳一個參數就是self實例自己,一般不這麽玩,使用print(Person.type)看到這個類方法

2.3、實例化

實例化:類名加括號就是實例化,會自動觸發__init__函數的運行,可以用它來為每個實例定制自己的特征

class Person:
    "這是個人的類"
    role = "person"   #數據屬性
    
    def type(self):     #函數屬性(類中的方法)
        print("yellow race"


p1 = Person() #實例化
p1.role  #查看對象屬性
p1.type()    #調用類方法
#補充:特殊的類屬性
類名.__name__# 類的名字(字符串)
類名.__doc__# 類的文檔字符串
類名.__base__# 類的第一個父類(在講繼承時會講)
類名.__bases__# 類所有父類構成的元組(在講繼承時會講)
類名.__dict__# 類的字典屬性
類名.__module__# 類定義所在的模塊
類名.__class__# 實例對應的類(僅新式類中)

2.4、小結

class 類名:
    def __init__(self,參數1,參數2):
        self.對象的屬性1 = 參數1
        self.對象的屬性2 = 參數2

    def 方法名(self):pass

    def 方法名2(self):pass

對象名 = 類名(1,2)  #對象就是實例,代表一個具體的東西
                  #類名() : 類名+括號就是實例化一個類,相當於調用了__init__方法
                  #括號裏傳參數,參數不需要傳self,其他與init中的形參一一對應
                  #結果返回一個對象
對象名.對象的屬性1   #查看對象的屬性,直接用 對象名.屬性名 即可
對象名.方法名()     #調用類中的方法,直接用 對象名.方法名() 即可

self:在實例化時自動將對象/實例本身傳給__init__的第一個參數

2.5、類和實例屬性的增刪改查

class Person:
    role = "person"
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def running(self):
        print("%s is running" %self.name)
    def eat_food(self,food):
        print("%s eat %s" %(self.name,food))
    def age(self):
        print("%s is %s" %(self.name,self.age))
#類屬性
print(Person.role)   #查看
Person.role = "dog"   #修改
Person.job = "doctor" #增加
del Person.job   #刪除

def test(self):
    print("test")
Person.test = test  #增加

#實例屬性
p1 = Person("crazy",18)
print(p1.__dict__)
print(p1.name)   #查看
p1.job = "xxx" #增加
print(p1.__dict__)

def test(self):
    print("test")
p1.test=test
p1.test(p1)  #需要增加參數

#實例只有數據屬性,其調用的是類的方法,但是實例也可以增加自己的方法(不同的實例去調的類方法相同,節省內存)

2.6、面向對象設計,有興趣看下面一個例子:

def cl(name,age,job):
    def jump(cl):
        print("%s 正在跳" %cl["name"] )
    def run(cl):
        print("%s 今年 %s 做 %s "%(cl["name"],cl["age"],cl["job"]))
    def init(name,age,job):
        per = {
            "name":name,
            "age":age,
            "job":job,
            "run":run,
            "jump":jump
        }
        return per
    return init(name,age,job)

c1 =cl("jojo",18,"xxx")
#{‘name‘: ‘dave‘, ‘age‘: 18, ‘job‘: ‘xxx‘, ‘run‘: <function cl.<locals>.run at 0x02EA3930>, ‘jump‘: <function cl.<locals>.jump at 0x02EA38E8>}
c1["run"](c1)
#jojo 今年 18 做 xxx

三、面向對象三特性(封裝、繼承和多態)

3.1、繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類

class Parent1: #定義父類1
    pass

class Parent2: #定義父類2
    pass

class Sub1(Parent1): #單繼承
    pass

class Sub2(Parent1,Parent2): #python支持多繼承,用逗號分隔開多個繼承的類
    pass
#查看繼承
print(Sub2.__bases__ )

#(<class ‘__main__.Parent1‘>, <class ‘__main__.Parent2‘>)

提示:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類

3.1.1、繼承的重用

在開發過程中,如果我們定義了一個A類,然後又想新建立另外一個B類,但是類B的大部分內容與類A的相同時我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。

通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用,來看個例子:

#第一種方式
class Asia:
    def head(self):
        pass
    def body(self):
        pass
    def skin(self):
        print("yellow")
class Europe:
    def head(self):
        pass
    def body(self):
        pass
    def skin(self):
        print("white")
class African:
    def head(self):
        pass
    def body(self):
        pass
    def skin(self):
        print("black")
#第二種方式:
class Person:
    def head(self):
        pass
    def body(self):
        pass
class Asia(Person):
    def skin(self):
        print("yellow")
class Europe(Person):
    def skin(self):
        print("white")
class African(Person):
    def skin(self):
        print("black")

Python的類可以繼承多個類,Java和C#中則只能繼承一個類,Python的類如果繼承了多個類,那麽其尋找方法的方式有兩種,分別是:深度優先和C3算法(廣度優先)

  • 當類是經典類時,多繼承情況下,會按照深度優先方式查找。
  • 當類是新式類時,多繼承情況下,會按照C3算法去查詢

新式類與經典類:

  • 只有在python2中才分新式類和經典類,python3中統一都是新式類
  • 在python2中,沒有顯示繼承object類的類,以及該類的子類,都是經典類
  • 在python2中,聲明繼承object的類,以及該類的子類,都是新式類
  • 在python3中,無論是否繼承object,都默認繼承object,即python3中所有類均為新式類
#python2中
class new(object):pass   #新式類
class old:pass      #經典類

#python3中都是新式類
class new(object):pass       
class old:pass 
#經典類多繼承
class D:
    def dd(self):
        print ‘D‘
class C(D):
    def cc(self):
        print ‘C‘
class B(D):
    def bb(self):
        print ‘B‘
class A(B, C):
    def aa(self):
        print ‘A‘
a=A()
# 執行aa方法時
# 首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中沒有,則繼續去D類中找,如果D類中沒有,則繼續去C類中找,如果還是未找到,則報錯
# 所以,查找順序:A --> B --> D --> C
# 在上述查找aa方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找


#新式類多繼承
class D:
    def D(self):
        print(‘D‘)
class C(D):
    def C(self):
        print (‘C‘)
class B(D):
    def B(self):
        print (‘B‘)
class A(B, C):
    def A(self):
        print (‘A‘)
print(A.mro())  #查看新式類多繼承順序
#[<class ‘__main__.A‘>, <class ‘__main__.B‘>, <class ‘__main__.C‘>, <class ‘__main__.D‘>, <class ‘object‘>]

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

新式類:則根據C3算法,具體可以通過類名.mro()方法去查詢他的繼承順序。

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

3.1.2、接口與抽象類

繼承有兩種用途:1、繼承基類的方法,並且做出自己的改變或者擴展(代碼重用) 2、聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,由子類繼承接口類,並且實現接口中的功能。(實踐中,繼承的第一種含義意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合)

#模擬接口
class Osname:
    def Windows(self): #接口函數
        pass
    def Linux(self):   #接口函數
        pass

class Win(Osname):
    def Windows(self):
        print("windows")
    def Linux(self):
        print("linx")

class Lin(Osname):
    def Linux(self):
        print("linx")
    def Windows(self):
        print("windows")

在python中根本就沒有一個叫做interface的關鍵字,上面的代碼只是看起來像接口,其實並沒有起到接口的作用,子類完全可以不用去實現接口,這就用到了抽象類。什麽是抽象類?與java一樣,python也有抽象類的概念但是同樣需要借助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化

import abc  #利用abc模塊實現抽象類
class Osname(metaclass=abc.ABCMeta):
    @abc.abstractmethod  #定義抽象方法,無需實現功能
    def Windows(self):
        pass
    @abc.abstractmethod
    def Linux(self):
        pass

class Win(Osname): #子類繼承抽象類,但是必須定義Windows和Linux方法
    def Windows(self):
        print("windows")
    def Linux(self):
        print("linx")

class Lin(Osname):
    def Linux(self):
        print("linx")
    def Windows(self):
        print("windows")

class Error(Osname):  
    pass
error = Error()   #報錯沒有定義方法,無法實例化

3.1.5、子類中調用父類方法

  • 直接父類名.方法名(self,name,addr)
  • super().方法名(name,addr) 或者super(子類名,self).方法名()

#直接父類名.方法名(...)
class Parent:
    def __init__(self,name,job):
        self.name = name
        self.job = job
    def money(self):
        print("%s have 1元" %self.name)
class Son(Parent):
    def __init__(self,name,job,addr):
        Parent.__init__(self,name,job)
        self.addr = addr
    def xxx(self,dd):
        Parent.money(self)
        print(dd)
s1 = Son("x","xx","xxx")     
s1.xxx("2元")   #x have 1元   2元


#super().方法名()
class Parent:
    def __init__(self,name,job):
        self.name = name
        self.job = job
    def money(self):
        print("%s have 1元" %self.name)
class Son(Parent):
    def __init__(self,name,job,addr):
       super().__init__(name,job)
       #等同於super(Son,self).__init(name,job)   
       self.addr = addr
    def xxx(self):
        super().money()

s1 = Son("x","xx","xxx")
s1.xxx()

3.1.4、類的組合與對象之間交互

軟件重用的重要方式除了繼承之外還有另外一種方式,即:組合。組合指的是,在一個類中以另外一個類的對象作為數據屬性,稱為類的組合

class Company:
    def __init__(self,name,addr):
        self.name = name
        self.addr = addr
        self.contain = Department("技術部")

class Department:
    def __init__(self,name):
        self.name = name
        self.function = "打醬油"
    def makemoney(self):
        print("1毛錢1分鐘")


m= Company("xx","地球")
m.contain.makemoney() #1毛錢1分鐘
print(m.contain.name)  #技術部

組合與繼承都是有效地利用已有類的資源的重要方式,當類之間有顯著不同並且較小的類是較大的類所需要的組件時,用組合比較好

對象之間的交互,先來看個例子:

class Thishero:
    def __init__(self,name):
        self.name = name  #hero名稱
        self.attack = 98    #攻擊值
        self.life = 100      #生命值
    def attacko(self,enemy):  #enmy為敵人
        enemy.life-= self.attack     #攻擊後敵人的生命剩余值

class Otherhero:
    def __init__(self,name):
        self.name = name
        self.attack = 99
        self.life = 100
    def attacko(self,enemy):
        enemy.life-= self.attack


c1 = Thishero("關羽")
c2 = Otherhero("秦瓊")

c1.attacko(c2)   #關羽戰秦瓊
print(c2.life)   #秦瓊剩余生命值

3.2 多態 (不同的對象執行相同的方法,執行的邏輯不同)

多態指的是一類事物有多種形態,比如人類有亞洲人、歐洲人、外星人...文件有文本、執行等等

class Person(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def head(self):
        pass
class Asia(Person):
    def head(self):
        print("yellow")
class Europe(Person):
    def head(self):
        print("white")
class African(Person):
    def head(self):
        print("black")
asia = Asia()
europe = Europe()
african = African()

asia.head()
europe.head()
african.head()

def obj(obj):    #我們可以定義一個統一的接口來使用
    obj.head()

其實大家從上面多態性的例子可以看出,我們並沒有增加什麽新的知識,也就是說python本身就是支持多態性的。或者你說太坑了吧,多態不就是繼承嘛!

3.3、封裝,顧名思義就是將內容封裝到某個地方,以後再去調用被封裝在某處的內容。所以在使用面向對象的封裝特性時,需要:

  • 將內容封裝到某處
  • 從某處調用被封裝的內容
class F:
    def __init__(self, name, age): #構造方法,創建對象時候自動執行
        self.name = name
        self.age = age
 
obj = F(‘jump‘, 18)  #將jump,18封裝到name和age中

print (obj.name)    # 直接調用obj對象的name屬性
print (obj.age)     # 直接調用obj對象的age屬性
 

由此我們可以看出對於面向對象的封裝來說,其實就是使用構造方法將內容封裝到 對象 中,然後通過對象調用封裝的內容

3.3.1、封裝與擴展性

封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足為慮。

class Run:
    def __init__(self,a,b,c):
        self.a = a
        self.__b = b    #(隱藏,外部可以通過接口或者直接調用)
        self._c = c  #(只是約定隱藏,外部仍可以調用)
    def run(self):
        return (self.a*self.__b*self._c)


r = Run(1,2,3)
print(r.run())
print(r._c)   #
print(r._Run__b) #直接調用隱藏

#隱藏屬性 (兩種方式外部訪問)
1、def out(self):(外部調用,接口)    
     return __self.xxx
2、obj._類名__xxx    obj._classname__xxx

  

  

Python基礎之(面向對象初識)