前言:
python物件導向的三大特性:繼承,封裝,多型。
1. 封裝: 把很多資料封裝到⼀個物件中. 把固定功能的程式碼封裝到⼀個程式碼塊, 函式, 物件, 打包成模組. 這都屬於封裝的思想. 具體的情況具體分析. 比如. 你寫了⼀個很⽜B的函式. 那這個也可以被稱為封裝. 在⾯向物件思想中. 是把⼀些看似⽆關緊要的內容組合到⼀起統⼀進⾏儲存和使⽤. 這就是封裝.
2. 繼承: ⼦類可以⾃動擁有⽗類中除了私有屬性外的其他所有內容. 說⽩了, ⼉⼦可以隨便⽤爹的東⻄. 但是朋友們, ⼀定要認清楚⼀個事情. 必須先有爹, 後有⼉⼦. 順序不能亂, 在python中實現繼承非常簡單. 在宣告類的時候, 在類名後⾯新增⼀個⼩括號,就可以完成繼承關係. 那麼什麼情況可以使⽤繼承呢? 單純的從程式碼層⾯上來看. 兩個類具有相同的功能或者特徵的時候. 可以採⽤繼承的形式. 提取⼀個⽗類, 這個⽗類中編寫著兩個類相同的部分. 然後兩個類分別取繼承這個類就可以了. 這樣寫的好處是我們可以避免寫很多重複的功能和程式碼. 如果從語義中去分析的話. 會簡單很多. 如果語境中出現了x是⼀種y. 這時, y是⼀種泛化的概念. x比y更加具體. 那這時x就是y的⼦類. 比如. 貓是⼀種動物. 貓繼承動物. 動物能動. 貓也能動. 這時貓在建立的時候就有了動物的"動"這個屬性. 再比如, ⽩骨精是⼀個妖怪. 妖怪天⽣就有⼀個比較不好的功能叫"吃⼈", ⽩骨精⼀出⽣就知道如何"吃⼈". 此時 ⽩骨精繼承妖精.
3. 多型: 同⼀個物件, 多種形態. 這個在python中其實是很不容易說明⽩的. 因為我們⼀直在⽤. 只是沒有具體的說. 比如. 我們建立⼀個變數a = 10 , 我們知道此時a是整數型別. 但是我們可以通過程式讓a = "alex", 這時, a⼜變成了字串型別. 這是我們都知道的. 但是, 我要告訴你的是. 這個就是多型性. 同⼀個變數a可以是多種形態。
一 封裝
封裝,顧名思義就是將內容封裝到某個地方,以後再去呼叫被封裝在某處的內容。
所以,在使用物件導向的封裝特性時,需要:
- 將內容封裝到某處
- 從某處呼叫被封裝的內容
第一步:將內容封裝到某處
self 是一個形式引數,當執行 obj1 = Foo('wupeiqi', 18 ) 時,self 等於 obj1
當執行 obj2 = Foo('alex', 78 ) 時,self 等於 obj2
所以,內容其實被封裝到了物件 obj1 和 obj2 中,每個物件中都有 name 和 age 屬性,在記憶體裡類似於下圖來儲存。
第二步:從某處呼叫被封裝的內容
呼叫被封裝的內容時,有兩種情況:
- 通過物件直接呼叫
- 通過self間接呼叫
1、通過物件直接呼叫被封裝的內容
上圖展示了物件 obj1 和 obj2 在記憶體中儲存的方式,根據儲存格式可以如此呼叫被封裝的內容:物件.屬性名
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
obj1 = Foo('wupeiqi', 18)
print obj1.name # 直接呼叫obj1物件的name屬性
print obj1.age # 直接呼叫obj1物件的age屬性
obj2 = Foo('alex', 73)
print obj2.name # 直接呼叫obj2物件的name屬性
print obj2.age # 直接呼叫obj2物件的age屬性
2、通過self間接呼叫被封裝的內容
執行類中的方法時,需要通過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('wupeiqi', 18)
obj1.detail() # Python預設會將obj1傳給self引數,即:obj1.detail(obj1),所以,此時方法內部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18
obj2 = Foo('alex', 73)
obj2.detail() # Python預設會將obj2傳給self引數,即:obj1.detail(obj2),所以,此時方法內部的 self = obj2,即:self.name 是 alex ; self.age 是 78
綜上所述,對於物件導向的封裝來說,其實就是使用構造方法將內容封裝到 物件 中,然後通過物件直接或者self間接獲取被封裝的內容。
二 多型
多型,同一個物件,多種形態。python預設支援多型。
# 在java或者c#定義變數或者給函式傳值必須定義資料型別,否則就報錯。
def func(int a):
print('a必須是數字')
# 而類似於python這種弱定義類語言,a可以是任意形態(str,int,object等等)。
def func(a):
print('a是什麼都可以')
# 再比如:
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虛擬碼實現Java或C # 的多型
多型舉例
python中有一句諺語說的好,你看起來像鴨子,那麼你就是鴨子。
對於程式碼上的解釋其實很簡答:
class A:
def f1(self):
print('in A f1')
def f2(self):
print('in A f2')
class B:
def f1(self):
print('in A f1')
def f2(self):
print('in A f2')
obj = A()
obj.f1()
obj.f2()
obj2 = B()
obj2.f1()
obj2.f2()
# A 和 B兩個類完全沒有耦合性,但是在某種意義上他們卻統一了一個標準。
# 對相同的功能設定了相同的名字,這樣方便開發,這兩個方法就可以互成為鴨子型別。
# 這樣的例子比比皆是:str tuple list 都有 index方法,這就是統一了規範。
# str bytes 等等 這就是互稱為鴨子型別。
三 類的約束
⾸先, 你要清楚. 約束是對類的約束.
用一個例子說話:
公司讓小明給他們的網站完善一個支付功能,小明寫了兩個類,如下:
class QQpay:
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay:
def pay(self,money):
print('使用阿里支付%s元' % money)
a = Alipay()
a.pay(100)
b = QQpay()
b.pay(200)
但是上面這樣寫不太放方便,也不合理,老大說讓他整改,統一一下付款的方式,小明開始加班整理:
class QQpay:
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay:
def pay(self,money):
print('使用阿里支付%s元' % money)
def pay(obj,money): # 這個函式就是統一支付規則,這個叫做: 歸一化設計。
obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
寫了半年的介面,小明終於接了大專案了,結果公司沒品位,招了一個野生的程式設計師春哥接替小明的工作,老大給春哥安排了任務,讓他寫一個微信支付的功能:
class QQpay:
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay:
def pay(self,money):
print('使用阿里支付%s元' % money)
class Wechatpay: # 野生程式設計師一般不會看別人怎麼寫,自己才是最好,結果......
def fuqian(self,money):
print('使用微信支付%s元' % money)
def pay(obj,money):
obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
c = Wechatpay()
c.fuqian(300)
結果春哥,受懲罰了,限期整改,那麼春哥,發奮圖強,看了太白教你學python的相關資料,重新梳理的程式碼:
class Payment: """ 此類什麼都不做,就是制定一個標準,誰繼承我,必須定義我裡面的方法。 """
def pay(self,money):pass
class QQpay(Payment):
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay(Payment):
def pay(self,money):
print('使用阿里支付%s元' % money)
class Wechatpay(Payment):
def fuqian(self,money):
print('使用微信支付%s元' % money)
def pay(obj,money):
obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
c = Wechatpay()
c.fuqian(300)
但是,這樣還會有問題,如果再來野生程式設計師,他不看其他的支付方式,也不知道為什麼繼承的類中要定義一個沒有意義的方法,所以他會是會我行我素:
class Payment:
""" 此類什麼都不做,就是制定一個標準,誰繼承我,必須定義我裡面的方法。
"""
def pay(self,money):pass
class QQpay(Payment):
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay(Payment):
def pay(self,money):
print('使用阿里支付%s元' % money)
class Wechatpay(Payment):
def fuqian(self,money):
print('使用微信支付%s元' % money)
def pay(obj,money):
obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
c = Wechatpay()
c.fuqian(300)
所以此時我們要用到對類的約束,對類的約束有兩種:
\1. 提取⽗類. 然後在⽗類中定義好⽅法. 在這個⽅法中什麼都不⽤⼲. 就拋⼀個異常就可以了. 這樣所有的⼦類都必須重寫這個⽅法. 否則. 訪問的時候就會報錯.
\2. 使⽤元類來描述⽗類. 在元類中給出⼀個抽象⽅法. 這樣⼦類就不得不給出抽象⽅法的具體實現. 也可以起到約束的效果.
先用第一種方式解決:
class Payment:
"""
此類什麼都不做,就是制定一個標準,誰繼承我,必須定義我裡面的方法。
"""
def pay(self,money):
raise Exception("你沒有實現pay方法")
class QQpay(Payment):
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay(Payment):
def pay(self,money):
print('使用阿里支付%s元' % money)
class Wechatpay(Payment):
def fuqian(self,money):
print('使用微信支付%s元' % money)
def pay(obj,money):
obj.pay(money)
a = Alipay()
b = QQpay()
c = Wechatpay()
pay(a,100)
pay(b,200)
pay(c,300)
第二種方式:引入抽象類的概念處理。
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta): # 抽象類 介面類 規範和約束 metaclass指定的是一個元類
@abstractmethod
def pay(self):pass # 抽象方法
class Alipay(Payment):
def pay(self,money):
print('使用支付寶支付了%s元'%money)
class QQpay(Payment):
def pay(self,money):
print('使用qq支付了%s元'%money)
class Wechatpay(Payment):
# def pay(self,money):
# print('使用微信支付了%s元'%money)
def recharge(self):pass
def pay(a,money):
a.pay(money)
a = Alipay()
a.pay(100)
pay(a,100) # 歸一化設計:不管是哪一個類的物件,都呼叫同一個函式去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
w = Wechatpay()
pay(w,100) # 到用的時候才會報錯
# 抽象類和介面類做的事情 :建立規範
# 制定一個類的metaclass是ABCMeta,
# 那麼這個類就變成了一個抽象類(介面類)
# 這個類的主要功能就是建立一個規範
總結: 約束. 其實就是⽗類對⼦類進⾏約束. ⼦類必須要寫xxx⽅法. 在python中約束的⽅式和⽅法有兩種:
1. 使⽤抽象類和抽象⽅法, 由於該⽅案來源是java和c#. 所以使⽤頻率還是很少的
2. 使⽤⼈為丟擲異常的⽅案. 並且儘量丟擲的是NotImplementError. 這樣比較專業, ⽽且錯誤比較明確.(推薦)
四. super()深入瞭解
super是嚴格按照類的繼承順序執行!!!
class A:
def f1(self):
print('in A f1')
def f2(self):
print('in A f2')
class Foo(A):
def f1(self):
super().f2()
print('in A Foo')
obj = Foo()
obj.f1()
super可以下一個類的其他方法
class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super().f1()
print('in Info f1')
obj = Info()
obj.f1()
'''
in Bar
in Foo
in Info f1
'''
print(Info.mro()) # [<class '__main__.Info'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <class '__main__.A'>, <class 'object'>]
super()嚴格按照類的mro順序執行
class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super(Foo,self).f1()
print('in Info f1')
obj = Info()
obj.f1()