python核心編程-第四章-深刻類和對象

第四章、深刻類和對象

4.1鴨子類型

當看到一隻鳥走起來像鴨子、游泳像鴨子、叫起來也像鴨子,那麼這隻鳥就能夠被稱爲鴨子python

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()web

對於判斷對象是否能夠調用某函數,更好的方法是去判斷其是不是某種類型,而不是該對象是否有某個屬性。redis

# 在某些狀況下但願斷定某個對象的類型,咱們須要強制某個子類必須實現某些方法

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")

報錯以下示:算法

1606741217010

# 做以下更改
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"))

1606741758636

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…
'''

鑽石繼承(菱形繼承)會帶來什麼問題?.net

多重繼承容易致使鑽石繼承(菱形繼承)問題,上邊代碼實例化 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&timestamp=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算法)

img

如計算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實戰測試: 案例一

多繼承UML圖:

# 備註: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實戰測試:案例二

這裏寫圖片描述

結果參考:

img

相關文章
相關標籤/搜索