1. 程式人生 > >python粗談面向對象(二) 百日築基(九)

python粗談面向對象(二) 百日築基(九)

icm 也不能 派生 以及 為什麽 是否 最終 bsp 內部

淺談super()

super並不是一個函數,是一個類名,形如super(B, self)事實上調用了super類的初始化函數,產生了一個super對象;Python的多繼承類是通過mro的方式來保證各個父類的函數被逐一調用,而且保證每個父類函數只調用一次(如果每個類都使用super),並且按照mro序列一次調用。下面是一個小練習:

class A:
    def fun(self):
        print(in A)
class B(A):
    def fun(self):
        super().fun()
        print(in B
) class C(A): def fun(self): print(in C) class D(B,C): def fun(self): super().fun() print(in D) print(D.mro()) # 打印mro序列 obj = D() obj.fun() # 打印內容如下 [<class __main__.D>, <class __main__.B>, <class __main__.C>, <class __main__.A
>, <class object>] in C in B in D

從打印結果我們可以知道super並不是簡單的按照調用父類那麽簡單。它是按照mro序列的排序方式調用的。在D類fun中super指向B類,在B類的fun方法中super指向下一個mro序列也就是C類。所以最終打印順序是C類的fun B類的fun最後是D類的fun。

在看一個簡單的示例:

class A:
    def fun(self):
        print(in A)
class B(A):
    def fun(self):
        super().fun()
        
print(in B) class C(A): def fun(self): print(in C) class D(B,C): def fun(self): super(B,self).fun() # 跳過B類,使用下一個mro序列 print(in D) print(D.mro()) # 打印mro序列 obj = D() obj.fun() # 打印內容如下 [<class __main__.D>, <class __main__.B>, <class __main__.C>, <class __main__.A>, <class object>] in C in D

super(B,self).fun()函數是表示跳過B類。使用下一個mro序列也就是C類。

面向對象之類成員

類的成員分為兩種形式:

  • 公有成員:在任何地方都可以訪問。
  • 私有成員:只能在類的內部訪問。

類屬性

類的靜態字段(靜態屬性):

  • 公有靜態字段:類可以訪問,類內部可以訪問,派生類中可以訪問。
  • 私有靜態字段:僅類內部可以訪問。

訪問類的公有字段:

class A:
    name = "公有靜態字段"
    def func(self):
        print(A.name)
class B(A):
    def show(self):
        print(B.name)
print(A.name)   # 類訪問
obj_A = A()
obj_A.func()     # 類內部訪問
obj_B = B()
obj_B.show() # 派生類中訪問

訪問類的私有字段:

class A:
    __name = "私有靜態字段"
    def func(self):
        print(A.__name)
class B(A):
    def show(self):
        print(A.__name)
# print(A.__name)     # 不可以在類外訪問
obj_A = A()
obj_A.func()     # 可以在類內部訪問
# obj_B = B()
# obj_B.show() # 不可以在派生類中訪問

對象屬性

  • 公有普通字段:對象可以訪問;類內部可以訪問;派生類中可以訪問
  • 私有普通字段:僅類內部可以訪問;

訪問對象公有字段:

class A:
    def __init__(self):
        self.foo = "對象公有字段"
    def func(self):
        print(self.foo) # 類內部訪問
class B(A):
    def show(self):
        print(self.foo) # 子類中訪問父類對象的公有字段
obj = A()
print(obj.foo)    # 通過對象訪問
obj.func()  # 類內部訪問

obj_B = B()
obj_B.show()  # 子類中訪問父類對象的公有字段

# 打印內容如下
對象公有字段
對象公有字段
對象公有字段

訪問對象私有屬性:

class A:
    def __init__(self):
        self.__foo = "對象私有字段"
    def func(self):
        print(self.__foo) # 類內部訪問
obj = A()
obj.func()  # 類內部訪問

#打印內容如下
對象私有字段

類方法

  • 公有方法:對象可以訪問,類內部可以訪問,派生類中可以訪問。
  • 私有方法:僅類內部可以訪問。

訪問公有方法:

class A:
    def fun(self):
        print("公有方法 A  fun")
class B(A):
    def show(self):
        print("公有方法 B show")
    def func(self):
        self.show()
obj = B()
obj.show()  # 通過對象訪問
obj.func()  # 類內部訪問
obj.fun()  # 子類中訪問父類方法

# 打印內容如下
公有方法 B show
公有方法 B show
公有方法 A  fun

在類中訪問私有方法:

class A:
    def __show(self):
        print("私有方法 A show")
    def func(self):
        self.__show()
obj = A()
obj.func()  # 類內部訪問

# 打印內容如下
私有方法 A show

總結:

對於這些私有成員來說,他們只能在類的內部使用,不能再類的外部以及派生類中使用。

如果非要訪問私有成員的話,我們可以通過類名.__dict__查看類的所有屬性和方法。如下圖所示:

技術分享圖片

由上圖我們可以看出私有方法只不過是Python在前面加了_類名__方法的方式進行了簡單的加密。所以雖然是私有方法或者私有屬性,我們還是可以用對象或者類在類的外部進行調用。但既然我們把它定義成私有屬性,就表示我們只想在類的內部調用而不打算在類的外部調用。所以沒有必要定義了私有屬性又在外部調用。

關於類的方法從類型上分為以下幾種:

實例方法:從名字上看就可以知道主要是給實例對象調用的,第一個參數必須是實例對象,這也應該沒什麽異議畢竟是給實例使用的,參數名一般約定俗成為“self”,如果你看它不順眼也可以改成自己喜歡的。通過它來傳遞實例的屬性和方法。主要由實例對象調用,雖然類也可以調用,但一般不建議。

類方法: 從名字上也可以看出它主要是給類使用的,使用裝飾器@classmethod。第一個參數必須是當前類,該參數名一般約定為“cls”,一樣如果你不習慣cls可以改成自己喜歡的,通過它來傳遞類的屬性和方法,主要由類調用,雖然實例對象也可以調用,但一般不建議。

靜態方法:這是一個特殊的方法,它除了在類空間內創建了一個函數外,和類沒有任何關系,使用裝飾器@staticmethod。參數隨意,沒有“self”和“cls”參數這些俗套的東東,如果想要在靜態方法中調用類的成員或者對象的成員需要將類或者對象傳遞給靜態方法。實例對象和類對象都可以調用。

雙下方法:這也是個特殊方法,他是解釋器提供的由雙下劃線加方法名加雙下劃線 __方法名__的具有特殊意義的方法,雙下方法主要是python源碼程序員使用的,我們在開發中盡量不要使用雙下方法,但是深入研究雙下方法,有益於我們閱讀源碼。

實例方法:

class A:
    name = "xiao ming"
    def fun(self):     # 實例方法
        self.__fun2()  # 調用私有實例方法
    def __fun2(self):  # 私有實例方法
        print("我是私有的實例方法")
    def fun3(self):
        print("我是公有的實例方法")
obj = A()
obj.fun()
A.fun3("我必須傳個參數")
A.fun("我必須傳個參數")

# 打印內容如下
我是私有的實例方法
我是公有的實例方法
AttributeError: str object has no attribute _A__fun2

類雖然可以調用實例方法,但是必須要傳遞個參數給實例方法,如果實例方法中在調用其它的實例方法,無論調用的是公有實例方法還是私有實例方法都會出現問題,因為這些方法需要參數,通過類的方式無法傳遞參數所以會報錯。下面是圖片應該看的更清晰些,所以說實例方法就是給實例用的,類就不要增加存在感了。如果有特殊情況需要類參與那就使用類方法。不要和實例方法混在一起。

實例對象在調用方法時就不需要傳遞參數,這是因為Python為我們隱式的把實例對象空間地址傳給了實例方法,所以實例對象在調用實例方法時不會報錯,因為Python已經為我們將參數隱式的傳遞給了實例方法。只是我們沒看到,所以說眼睛看到的未必就是真實的。

技術分享圖片

類方法:

class A:
    name = "xiao ming"
    @classmethod
    def fun(cls):     # 類方法
        cls.__fun2()  # 調用私有類方法
        print(cls)    # 打印cls內存地址
    @classmethod
    def __fun2(cls):  # 類私有方法
        print("我是類的私有方法")
    @classmethod
    def fun3(cls):
        print("我是公有的實例方法")
obj = A()
obj.fun()  # 對象調用類方法
A.fun3()   # 類調用類方法
A.fun()    # 類調用類方法
print(A)

# 打印內容如下
我是類的私有方法
<class __main__.A>  # 通過實例對象調用的類方法
我是公有的實例方法
我是類的私有方法
<class __main__.A>
<class __main__.A>

從打印結果我們可以知道,類的實例對象也可以正常調用類方法,並且Python為我們將類A隱式的傳遞給了類方法,而不是將實例對象空間傳遞給了類方法。所以我們不能在類方法中使用對象的屬性和方法,除非我們將實例對象空間傳遞給類方法,這就需要在定義類方法時,給類方法在加個形參,然後使用實例對象顯式的將對象空間傳遞給類方法。所以說既然實例對象有自己的實例方法就不要和類方法湊熱鬧了,這就是不建議用實例對象調用類方法的原因。

小示例:統計創建實例對象的個數。

class A:
    count = 0
    def __init__(self):
        A.obj_count()  # 統計創建了多少個實例對象
    @classmethod
    def obj_count(cls):
        cls.count += 1
obj_1 = A()
obj_2 = A()
obj_3 = A()
print(A.count)

# 打印內容如下
3

靜態方法:

class A:
   @staticmethod
   def fun():
       print("我是靜態函數")
obj = A()
obj.fun()
A.fun()

關於類的靜態方法沒什麽好說的,就是在類空間內創建了一個與類不發生任何關系的函數,也不能說一點關系沒有,畢竟是在類空間創建的。類和實例化對象都可以正常調用。

雙下方法:

我們知道在Python中一切皆是對象,而我們又知道對象是類實例化出來的,所以Python中的對象必然都是通過某個具體類實例化出來的。例如:

技術分享圖片

我們可以知道str_1是str類的實例化對象,所以str_1可以使用str類中的所有方法,而str類繼承object類所以str_1也可以使用object類中的方法。如果我們想要獲取字符串的長度可以直接使用len(字符串),這是為什麽呢?,那麽len又屬於哪個類的方法呢?我們做個簡單的示例:

class A:
    def __len__(self):
        print("計算長度")
obj = A()
len(obj)

下面是圖片,應該更好理解:

技術分享圖片

好的既然不能解釋,那麽我們就讓它能解釋。

class A:
    def __len__(self):
        return 4
obj = A()
print(len(obj))

# 打印內容如下
4

我們可以發現這回沒有報錯了,那如果我在類A中在定義一個用於統計字符串長度的函數__len__,是不是類對象在統計屬性長度時就可以調用本類中len功能了呢?答案是理論上可以,你可以單獨創建個數據類型然後不繼承object類,繼承你寫的類這樣就可以調用你寫的len了。

關於雙下劃線方法我們要知道幾個主要的如下:

__new__:在實例化對象時為對象開辟內存空間。

class A:
    def __init__(self):
        self.x = 1
        print(in __init__)
obj = A()
print(obj.x)

 # 打印內容如下
in __init__
1

下面演示一個沒開辟空間的實例化對象:

class A:
    def __init__(self):
        self.x = 1  # 為實例對象封裝屬性
        print(in __init__)
    def __new__(cls, *args, **kwargs):
        print("in __new__")
obj = A()
obj.name = "Hello World"  # 為實例對象封裝屬性
print(obj.x)

打印如下圖所示:

技術分享圖片

觸發了__new__後並沒有執行__init__函數,所以也就沒有給obj對象封裝x這個屬性,當調用obj.x這個屬性時,找不到也是自然,但是給對象封裝name屬性時也失敗,究其原因就是實例對象在內存中沒有空間,所以無法為其封裝屬性。

下面我們在類A中的__new__中調用object的__new__為對象開辟內存。

class A:
    def __init__(self):
        self.x = 1  # 為實例對象封裝屬性
        print(in __init__)
    def __new__(cls, *args, **kwargs):
        print("in __new__")
        return object.__new__(cls)  # 調用object的__new__為對象開辟空間
obj = A()
obj.name = "Hello World"  # 為實例對象封裝屬性
print(obj.x)

打印內容如下:

技術分享圖片

下面我們來演示個單實例的代碼:什麽是單實例?單實例就是類創建N個對象,但是這N個對象都使用一塊內存空間。

class A:
    instance_flag = None  # 如果有創建一個實例對象就將空間賦值給instance_flag
    def __new__(cls, *args, **kwargs):
        if not cls.instance_flag:  
            cls.instance_flag = object.__new__(cls)
        return cls.instance_flag
obj_1 = A()
obj_2 = A()
obj_3 = A()
print(obj_1)
print(obj_2)
print(obj_3)

# 打印內容如下
<__main__.A object at 0x0000000002868588>
<__main__.A object at 0x0000000002868588>
<__main__.A object at 0x0000000002868588>
單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統中一個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。
【采用單例模式動機、原因】
對於系統中的某些類來說,只有一個實例很重要,例如,一個系統中可以存在多個打印任務,但是只能有一個正在工作的任務;一個系統只能有一個窗口管理器或文件系統;一個系統只能有一個計時工具或ID(序號)生成器。如在Windows中就只能打開一個任務管理器。如果不使用機制對窗口對象進行唯一化,將彈出多個窗口,如果這些窗口顯示的內容完全一致,則是重復對象,浪費內存資源;如果這些窗口顯示的內容不一致,則意味著在某一瞬間系統有多個狀態,與實際不符,也會給用戶帶來誤解,不知道哪一個才是真實的狀態。因此有時確保系統中某個對象的唯一性即一個類只能有一個實例非常重要。
如何保證一個類只有一個實例並且這個實例易於被訪問呢?定義一個全局變量可以確保對象隨時都可以被訪問,但不能防止我們實例化多個對象。一個更好的解決辦法是讓類自身負責保存它的唯一實例。這個類可以保證沒有其他實例被創建,並且它可以提供一個訪問該實例的方法。這就是單例模式的模式動機。
【單例模式優缺點】
【優點】
一、實例控制
單例模式會阻止其他對象實例化其自己的單例對象的副本,從而確保所有對象都訪問唯一實例。
二、靈活性
因為類控制了實例化過程,所以類可以靈活更改實例化過程。
【缺點】
一、開銷
雖然數量很少,但如果每次對象請求引用時都要檢查是否存在類的實例,將仍然需要一些開銷。可以通過使用靜態初始化解決此問題。
二、可能的開發混淆
使用單例對象(尤其在類庫中定義的對象)時,開發人員必須記住自己不能使用new關鍵字實例化對象。因為可能無法訪問庫源代碼,因此應用程序開發人員可能會意外發現自己無法直接實例化此類。
__call__:實例化對象()  或者 類名()()會觸發

class A:
    def __init__(self):
        pass
    def __call__(self, *args, **kwargs):
        print(__call__)

obj = A()  # 實例化對象
obj()    # 執行 __call__
A()()  # 執行 __call__
__call__
__call__
__item__:當以字典的形式操作實例對象時會觸發。
class A:
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        print("添加屬性,修改屬性時,激活我")
        self.__dict__[key]=value
    def __delitem__(self, key):
        print(del obj[key]刪除值時,我執行)
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print(刪除屬性時,我執行)
        self.__dict__.pop(item)

obj_1=A(小明)
obj_1[age]=18         # 新增屬性
obj_1[age1]=19        # 修改屬性
del obj_1.age1          # 刪除屬性
del obj_1[age]        # 刪除字典的值 
print(obj_1.__dict__)
添加屬性,修改屬性時,激活我
添加屬性,修改屬性時,激活我
刪除屬性時,我執行
del obj[key]刪除值時,我執行
{name: 小明}
好了雙下方法就到這裏吧。

property

將一個類的函數定義成屬性,對象再去使用的時候,可以直接使用對象.屬性的方式來執行這個函數,從表面無法判斷是屬性還是方法。

class A:
    @property
    def fun(self):
        print("我是被分裝成屬性的函數")
obj = A()
obj.fun   # 調用屬性

# 打印內容如下
我是被分裝成屬性的函數
如果單從調用fun來看根本看不出來fun到底是函數還是一個真正的屬性。
那麽我們對property都有哪些操作呢?可以獲取property,設置property和刪除property三種操作。這三種操作有兩種實現方式,如下:

class A:
    @property
    def AAA(self):
        print(get的時候運行我啊)
    @AAA.setter
    def AAA(self,value):
        print(set的時候運行我啊)
    @AAA.deleter
    def AAA(self):
        print(delete的時候運行我啊)
#只有在屬性AAA定義property後才能定義AAA.setter,AAA.deleter
f1=A()
f1.AAA        # 獲取屬性
f1.AAA=aaa  # 設置屬性
del f1.AAA    # 刪除屬性

# 打印內如下
get的時候運行我啊
set的時候運行我啊
delete的時候運行我啊
第二種方式

class B:
    def get_AAA(self):  # 獲取屬性時
        print(get的時候運行我啊)
    def set_AAA(self,value):  # 設置屬性
        print(set的時候運行我啊)
    def delete_AAA(self):   # 刪除屬性
        print(delete的時候運行我啊)
    AAA=property(get_AAA,set_AAA,delete_AAA) #內置property三個參數與get,set,delete一一對應

f1=B()
f1.AAA       # 獲取屬性
f1.AAA=aaa # 設置屬性
del f1.AAA   # 刪除屬性

# 打印內容如下
get的時候運行我啊
set的時候運行我啊
delete的時候運行我啊
下面是一個商品實例的應用:
class Goods(object):
    def __init__(self):
        self.price_1 = 100     # 原價
        self.discount_1 = 0.8  # 折扣
    @property
    def price(self):
        new_price = self.price_1 * self.discount_1  # 實際價格 = 原價 * 折扣
        return new_price
    @price.setter
    def price(self, value):
        self.original_price = value  # 重新設置價格
    @price.deleter
    def price(self):  # 刪除價格
        del self.original_price
obj = Goods()
obj.price         # 獲取商品價格
obj.price = 200   # 修改商品原價
del obj.price     # 刪除商品原價

isinstance和issubclass的區別

isinstance(a,b):判斷a是否是b類(或者b類的派生類)實例化的對象

如下代碼:

class A:
    pass
class B(A):
    pass
obj = B()
print(isinstance(obj,B))
print(isinstance(obj,A))

# 打印內容如下
True
True

issubclass(a,b): 只能判斷a類是否是b類的派生類。

class A:
    pass
class B(A):
    pass
class C(B):
    pass
print(issubclass(C,B))
print(isinstance(C,A))

# 打印內容如下
True
False

元類type。

按照Python的一切皆對象理論,類其實也是一個對象,那麽類這個對象是從哪裏實例化出來的呢?

class A:
    pass
print(isinstance(A, type))
print(isinstance(A, object))

print(isinstance(object,type)) # object是type的實例化對象
print(issubclass(type, object))# 而type又是object的子類

# 打印內如下
True
True
True
True

type元類是獲取該對象從屬於的類,而type類比較特殊,Python原則是:一切皆對象,其實類也可以理解為‘對象‘,而type元類又稱作構建類,python中大多數內置的類(包括object)以及自己定義的類,都是由type元類創造的。

* 而type類與object類之間的關系比較獨特:object是type類的實例,而type類是object類的子類,這種關系比較神奇無法使用python的代碼表述,因為定義其中一個之前另一個必須存在。所以這個只作為了解。有時間在研究。

python粗談面向對象(二) 百日築基(九)