python核心程式設計-第四章-深入類和物件
第四章、深入類和物件
4.1鴨子型別
當看到一隻鳥走起來像鴨子、游泳像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子
4.2抽象基類(abc模組)
判斷某一個物件是否有某一個屬性,hasattr()
class Company(object): def __init__(self, employee_list): self.employee = employee_list def __getitem__(self, item): return self.employee[item] def __len__(self): return len(self.employee) print(hasattr(Company, "__len__")) company = Company(['tom', 'bob', 'jane']) print(hasattr(company, "__len__"))
判斷某一個物件是否是某種型別,isinstance()
對於判斷物件是否可以呼叫某函式,更好的方法是去判斷其是否是某種型別,而不是該物件是否有某個屬性。
# 在某些情況下希望判定某個物件的型別,我們需要強制某個子類必須實現某些方法 from collections.abc import Sized class Company(object): def __init__(self, employee_list): self.employee = employee_list def __getitem__(self, item): return self.employee[item] def __len__(self): return len(self.employee) company = Company(['tom', 'bob', 'jane']) print(isinstance(company, Sized))
# 設計一個抽象基類,指定子類必須事項某些方法 # 實現一個web框架,整合cache(redis, cache, memorychache) class CacheBase(): def get(self, key): raise NotImplementedError def set(self, key, value): raise NotImplemnetedError class RedisCache(CacheBase): pass redis_cache = RedisCache() redis_cache.set("key", "value")
報錯如下示:
# 作如下更改
class RedisCache(CacheBase):
def __init__(self):
self.dic = {}
def get(self, key):
return self.dic[key] if key in self.dic else None
def set(self, key, value):
self.dic[key] = value
redis_cache = RedisCache()
redis_cache.set("key", "value")
print(redis_cache.get("key"))
4.3使用instance而不是type
- type判斷一個物件是誰的例項
- isinstance是判斷這個物件的繼承鏈,isinstance(a,b)如果a在b的下方就返回True,否則False
class A(object):
pass
class B(A):
pass
class C(B):
pass
c = C()
print(type(c)) # <class '__main__.C'>
print(type(C)) # <class 'type'>
print(isinstance(C, B)) # False
print(isinstance(C, object)) # True
4.4類屬性和例項屬性以及查詢順序
先例項屬性再類屬性
如果把例項看成是類,那麼類變數就是父類中的屬性,而例項變數是子類的屬性,子類中沒有父類有 會從父類中繼承,子類中有父類有 相當於覆蓋重寫
class A:
name = 'bb'
def __init__(self, x):
self.x = x
a1 = A(1)
a2 = A(2)
print(a1.name) #繼承類變數bb
print(a1.x) # 就是例項變數x的值
print(A.name) # 類變數bb
A.name = 'dd' # 更改了類變數
print(A.name) # 類變數發生更改dd
print(a1.name) # 例項的類變數也更改dd
a1.name = 'name'
print(a1.name) # 在此更改類變數
print(A.name) # 刪除了例項屬性
A.name = 'last' # 在此更改了類變數
del a1.name # 刪除了例項變數
print(a1.name) # 繼承類變數last
類變數和屬性的呼叫順序:
在python中,為了重用程式碼,可以使用在子類中使用父類的方法,但是在多繼承中可能會出現重複呼叫的問題,支援多繼承的面向物件程式設計都可能會導致鑽石繼承(菱形繼承)問題 ,如下示:
class A():
def __init__(self):
print("進入A…")
print("離開A…")
class B(A):
def __init__(self):
print("進入B…")
A.__init__(self)
print("離開B…")
class C(A):
def __init__(self):
print("進入C…")
A.__init__(self)
print("離開C…")
class D(B, C):
def __init__(self):
print("進入D…")
B.__init__(self)
C.__init__(self)
print("離開D…")
d = D()
# 實際上會先找B, 然後找A, 然後是C, 再找A
'''
進入D…
進入B…
進入A…
離開A…
離開B…
進入C…
進入A…
離開A…
離開C…
離開D…
'''
鑽石繼承(菱形繼承)會帶來什麼問題?
多重繼承容易導致鑽石繼承(菱形繼承)問題,上邊程式碼例項化 D 類後我們發現 A 前後進入了兩次。另外,假設 A 的初始化方法裡有一個計數器,那這樣 D 一例項化,A 的計數器就跑兩次(如果遭遇多個鑽石結構重疊還要更多),很明顯是不符合程式設計的初衷的(程式應該可控,而不能受到繼承關係影響)。
如何避免鑽石繼承(菱形繼承)問題?
為解決這個問題,Python 使用了一個叫“方法解析順序(Method Resolution Order,MRO)”的東西,還用了一個叫 C3 的演算法。
MRO 的順序基本就是:在避免同一類被呼叫多次的前提下,使用廣度優先和從左到右的原則去尋找需要的屬性和方法。有則呼叫,沒有就查詢新增到MRO中。
在繼承體系中,C3 演算法確保同一個類只會被搜尋一次。例子中,如果一個屬性或方法在 D 類中沒有被找到,Python 就會搜尋 B 類,然後搜尋 C類,如果都沒有找到,會繼續搜尋 B 的基類 A,如果還是沒有找到,則丟擲“AttributeError”異常。
可以使用 類名.mro 獲得 MRO 的順序(注:object 是所有類的基類,金字塔的頂端):
print(D.mro())
# [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
為了防止多繼承中的一些問題,使用C3演算法解決,它能夠保證呼叫鏈
中所有的類僅出現一次,也就是說每個節點後面的類都不繼承它,否則將它從呼叫鏈中刪除,D.mro()
檢視呼叫鏈。
而super()
的使用就是基於這條呼叫鏈,當使用super()時,會查詢呼叫鏈,找到當前類,呼叫下一個類,沒有則找下一個。super()預設是當前類,也可以寫類名
class A():
def __init__(self):
print("進入A…")
print("離開A…")
class B(A):
def __init__(self):
print("進入B…")
super().__init__()
print("離開B…")
class C(A):
def __init__(self):
print("進入C…")
super().__init__()
print("離開C…")
class D(B, C):
def __init__(self):
print("進入D…")
super().__init__()
print("離開D…")
d = D()
'''
進入D…
進入B…
進入C…
進入A…
離開A…
離開C…
離開B…
離開D…
'''
4.4.1mro序列
MRO的侷限性:型別衝突時, 子類改變了基類的方法搜尋順序。而 子類不能改變基類的方法搜尋順序。在 Python 2.2 的 MRO 演算法中並不能保證這種單調性,它不會阻止程式設計師寫出上述具有二義性的繼承關係,因此很可能成為錯誤的根源。
部落格:https://mp.weixin.qq.com/s?src=11×tamp=1606794886&ver=2739&signature=SRS5nobqUFoRtzuAsoe8pOknkOYeNxLZz90OnfG3sQtaaMcaI23ZYMSjmGLKogS997A1whxzOgqTugCvuNSlEz5akZ86QE4myAw2JboDp-DRHrsLiWKCj97Ld2lKTq&new=1
- MRO(Method Resolution Order: 方法解析順序) 是一個有序列表L,在類被建立時就計算出來。
- 通用的計算公式:
mro(Child(Base1, Base2)) = [Child] + merge(mro(Base1), mro(Base2), [Base1, Base2])
(其中Child繼承自Base1, Base2)
- 如果繼承至一個基類:class B(A)
這時B的mro序列為 :
mro( B )
= mro( B(A) )
= [B] + merge( mro(A) + [A] )
= [B] + merge( [A] + [A] )
= [B,A]
- 如果繼承至多個基類:class B(A1, A2, A3 …)
這時B的mro序列
mro(B)
= mro( B(A1, A2, A3 …) )
= [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )
= ...
PS: 計算結果為列表,列表中至少有一個元素即類自己,如上述示例[A1,A2,A3]。merge操作是C3演算法的核心。
(參考部落格:https://blog.csdn.net/u011467553/article/details/81437780)
4.4.2 表頭和表尾
- 表頭:
列表的第一個元素 - 表尾:
列表中表頭以外的元素集合(可以為空) - 示例
列表:[A, B, C]
表頭是A,表尾是B和C
4.4.3 列表之間的+操作
+操作:
list_ = ['A'] + ['B']
print(list_) # ['A', 'B']
4.4.4 merge操作(C3演算法)
如計算merge( [E,O], [C,E,F,O], [C] )
有三個列表 : list1 list2 list3
1 merge不為空,取出第一個列表列表list1的表頭E,進行判斷
各個列表的表尾分別是[O], [E,F,O],E在這些表尾的集合中,因而跳過當前當前列表
2 取出列表list2的表頭C,進行判斷
C不在各個列表的集合中,因而將C拿出到merge外,並從所有表頭刪除
merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
3 進行下一次新的merge操作 ......
merge( [E,O], [C,E,F,O], [C])
= [C] + merge( [E,O], [E,F,O] )
= [c] + [E] + merge( [O], [F, 0])
= [c] + [E] + [F] + merge( [O], [0])
= [C] + [E] + [F] + [0]
看看之前的
print(D.mro())
# [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
mro(D(B, C))
= [D] + merge(mro(B(A)), mro(C(A)), [B, C])
= [D] + merge([B] + merge(A(object), [A]), [C] + merge(A(object), [A]), [B, C])
= [D] + merge([B] + merge([A, object], [A]), [C] + merge([A, object], [A]), [B, C])
= [D] + merge([B] + [A, object], [C] + [A, object], [B, C])
= [D] + merge([B, A, object], [C, A, object], [B, C])
= [D] + [B] + merge([A, object], [C, A, object], [C])
= [D] + [B] + [C] + merge([A, object], [A, object])
= [D] + [B] + [C] + [A] + merge([object], [object])
= [D] + [B] + [C] + [A] + [object]
= [D, B, C, A, object]
= [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
4.4.5實戰測試: 案例一
# 備註:O==object, 如何計算mro(A) ?
mro(A)
= mro(A(B, C))
= [A] + merge(mro(B(D, E)), mro(C(E, F)), [B, C])
= [A] + merge([B] + merge(mro(D(O)), mro(E(O)), [D, E]), [C] + merge(mro(E(O)), mro(F(O)), [E, F]), [B, C])
= [A] + merge([B] + merge([D, O], [E, O], [D, E]), [C] + merge([E, O], [F, O], [E, F]), [B, C])
= [A] + merge([B] + [D, E, 0], [C] + [E, F, O], [B, C])
= [A] + merge([B, D, E, 0], [C, E, F, O], [B, C])
merge([D, O], [E, O], [D, E])
= [D] + merge([O], [E, O], [E])
= [D] + [E] + merge([O], [O])
= [D] + [E] + [O]
= [D, E, 0]
merge([E, O], [F, O], [E, F])
= [E] + merge([O], [F, O], [F])
= [E] + [F] + merge([O], [O])
= [E] + [F] + [O]
= [E, F, O]
= [A] + [B] + merge([D, E, 0], [C, E, F, O], [C])
= [A] + [B] + [D] + merge([E, 0], [C, E, F, O], [C])
= [A] + [B] + [D] + [C] + merge([E, 0], [E, F, O])
= [A] + [B] + [D] + [C] + [E] + merge([0], [F, O])
= [A] + [B] + [D] + [C] + [E] + [F]+ merge([0], [O])
= [A] + [B] + [D] + [C] + [E] + [F]+ [O]
= [A, B, D, C, E, F, O]
# 以上案例的程式碼測試
class D: pass
class E: pass
class F: pass
class B(D,E): pass
class C(E,F): pass
class A(B,C): pass
print("從A開始查詢:")
for s in A.__mro__:
print(s)
print("從B開始查詢:")
for s in B.__mro__:
print(s)
print("從C開始查詢:")
for s in C.__mro__:
print(s)
'''
從A開始查詢:
<class '__main__.A'>
<class '__main__.B'>
<class '__main__.D'>
<class '__main__.C'>
<class '__main__.E'>
<class '__main__.F'>
<class 'object'>
從B開始查詢:
<class '__main__.B'>
<class '__main__.D'>
<class '__main__.E'>
<class 'object'>
從C開始查詢:
<class '__main__.C'>
<class '__main__.E'>
<class '__main__.F'>
<class 'object'>
'''
規律總結: “從一至頂,有子先出”
4.4.6實戰測試:案例二
結果參考: