大師兄的Python學習筆記(二): 面向對象和類

大師兄的Python學習筆記(一): 基礎篇
大師兄的Python學習筆記(三): 模塊和包

三、面向過程和面向對象的思維方式

1.面向過程(Proceduce Oriented)
  • 如果想吃西紅柿炒雞蛋:
    step1: 打雞蛋
    step2: 切西紅柿
    step3: 熱鍋
    step4: 炒雞蛋
    step5: 炒西紅柿
    step6: 放鹽
    step7: 出鍋

  • 這種把任務切分成一系列子工作,自頂向下一步一步實現的方式叫做面向過程

2. 面向對象(Object Oriented)
  • 如果想吃西紅柿炒雞蛋:
    step1: 設計一個飯館的藍圖(類)
    飯館包含食材:西紅柿、雞蛋
    飯館包含調料:鹽
    飯館包含炊具:鍋
    飯館包含功能:炒菜
    step2: 根據藍圖建造一個飯館(對象)
    step3: 點西紅柿炒雞蛋這道菜
  • 這里我們實際上是將制作西紅柿炒雞蛋的過程封裝到對象飯館中。
  • 吃飯的人只需要對廚房下達命令,至于功能的實現方式則不用關心。
  • 這種將功能抽象封裝的方式叫做面向對象
3.面向對象的優缺點

1)易復用
建立廚房后,如果為一萬人提供西紅柿炒雞蛋,我們只需要下達一萬次命令即可,不用重復造輪子。
2)易維護
如果想吃黃瓜炒雞蛋,只需將食材中的西紅柿替換成黃瓜,不用重新設計整個流程。
3)易擴展
如果想吃西紅柿雞蛋蓋飯,只需要增加食材大米和蒸飯的功能,而并不需要重建一個廚房。
4)缺點:性能低于面向過程
如果只是一個人吃飯,蓋飯館就很浪費。

4.總結

1)面向過程適合簡單、少量的任務。
2)面向對象適合復雜、大量的任務。
3)面向對象并不能取代面向過程,面向對象的最底層是面向過程。

四、類和對象

1.類
  • 類就是具備某些共同特征的實體的集合,它是一種抽象的數據類型,它是對所具有相同特征實體的抽象。
  • 比如:西紅柿和雞蛋都屬于食材,食材是個抽象的概念,是一個類。
2.對象
  • 對象是具體的事物,單個的個體。
  • 比如:西紅柿和雞蛋可以是對象、鹽和鍋可以是對象、西紅柿炒雞蛋可以是對象,廚房也可以是對象。
3.類和對象的關系

類是對象的概括,對象是類的具體體現

五、在Python中實現類和對象

1.類的聲明
  • 用class關鍵字。
  • 類的成員只可以是屬性或方法。
  • 屬性可以看理解為類中的變量。
  • 方法可以看理解為類中的函數。
class <類名>():
     <類的成員> # 這里必須有縮進
>>>class FoodMaterial():
>>>    price = 10  # 屬性
>>>    def cut():   # 方法
>>>        print('我被切成片了!')    
2.類的實例化
  • 也就是將類實例化成為對象
<變量> = <類名>()
>>>class FoodMaterial():
>>>    price = 10  # 屬性
>>>    def cut():   # 方法
>>>        print('我被切成片了!')    
>>>tomato = FoodMaterial() # 這里將類實例化后存儲在變量中
3. 訪問類和對象的成員

1)訪問類的屬性和調用方法

方法一:
<類名>.<屬性名> # 訪問屬性
<類名>.<方法名>() # 調用方法
方法二:
getattr(<類名>,"<屬性名>")
getattr(<類名>,"<方法名>")()
>>>class FoodMaterial():
>>>    price = 10  
>>>    def cut():   
>>>        print('我被切成片了!')    
>>>print(FoodMaterial.price) # 訪問屬性
>>>10
>>>FoodMaterial.cut() # 調用方法
>>>我被切成片了!

2) 訪問對象的屬性和方法

方法一:
<對象名>.<屬性名> # 訪問屬性
<對象名>.<方法名>() # 調用方法
方法二:
getattr(<對象名>,"<屬性名>")
getattr(<對象名>,"<方法名>")()
>>>class FoodMaterial():
>>>    price = 10  
>>>    def cut(self):   # self我們后面說
>>>        print('我被切成片了!')    
>>>tomato = FoodMaterial() 
>>>print(tomato.price) # 訪問屬性
>>>10
>>>tomato.cut() # 調用方法
>>>我被切成片了!
4. 修改類和對象的屬性

1)修改類的屬性

方法一:<類名>.<屬性名> = <值> # 修改類屬性
方法二:setattr(<類名>,"<屬性名>",<值>)
>>>class FoodMaterial():
>>>    price = 10  
>>>tomato = FoodMaterial()
>>>FoodMaterial.price = 20
>>>print(FoodMaterial.price)
20
>>>print(tomato.price) # 這時對象的屬性也跟著改變了
20

2)修改對象的屬性

  • 如果對象的屬性被修改過,就不會跟著類的屬性同步了。
方法一:<對象名>.<屬性名> = <值> # 修改對象屬性
方法二:setattr(<對象名>,"<屬性名>",<值>)
>>>class FoodMaterial():
>>>    price = 10  
>>>tomato = FoodMaterial()
>>>tomato.price = 20
>>>print(tomato.price)
20
>>>print(FoodMaterial.price) # 這時類的屬性并沒有改變
10
>>>FoodMaterial.price = 30 # 如果這時我們再修改類的屬性
>>>print(tomato.price) # 對象的屬性不會再跟著改變
20
5. 增加類和對象的屬性

1)增加類的屬性

方法一:<類名>.<屬性名> = <值> 
方法二:setattr(<類名>,"<屬性名>",<值>)
>>>class FoodMaterial():
>>>    price = 10  
>>>tomato = FoodMaterial()
>>>FoodMaterial.colour = 'red'
>>>print(FoodMaterial.colour)
red
>>>print(tomato.colour) # 這時對象的屬性也跟著增加了
red

2)增加對象的屬性

方法一:<對象名>.<屬性名> = <值> # 增加對象屬性
方法二:setattr(<對象名>,"<屬性名>",<值>)
>>>class FoodMaterial():
>>>    price = 10  
>>>tomato = FoodMaterial()
>>>tomato.colour = 20
>>>print(tomato.colour)
red
>>>print(FoodMaterial.colour) # 這時類的屬性并沒有增加
AttributeError: type object 'FoodMaterial' has no attribute 'colour'
>>>FoodMaterial.colour = 'yellow' # 如果這時我們再修改類的屬性
>>>print(tomato.colour) # 對象的屬性不會改變
red
6. 刪除類和對象的屬性

1)刪除類的屬性

方法一:del <類名>.<屬性名>  # 刪除類屬性
方法二:delattr(<類名>,"<屬性名>")
>>>class FoodMaterial():
>>>    price = 10  
>>>    colour = 'red'
>>>tomato = FoodMaterial()
>>>del FoodMaterial.price
>>>print(FoodMaterial.price) # 刪除屬性
AttributeError: type object 'FoodMaterial' has no attribute 'price'
>>>print(tomato.colour) # 這時對象的屬性也被刪除了
AttributeError: type object 'FoodMaterial' has no attribute 'price'

2)刪除對象的屬性

  • 從下面的例子就可以看出,對象的屬性在沒有單獨定義的情況下,實際上是調用了類的屬性
  • 如果單獨為對象創建屬性,在屬性名相同的情況下,優先調用對象的屬性
方法一:del <對象名>.<屬性名> # 刪除對象屬性
方法二:delattr(<對象名>,"<屬性名>")
>>>class FoodMaterial():
>>>    price = 10  
>>>    colour = 'red'
>>>tomato = FoodMaterial()
>>>del tomato.price # 這里會提示tomato并沒有price屬性,實際上是調用的FoodMaterial得price屬性
AttributeError: price

>>>tomato.price = 20 # 為對象增加屬性
>>>print(tomato.price) # 覆蓋了類的屬性
20
>>>del tomato.price # 刪除了對象的屬性
>>>print(tomato.price) # 這里又調用了類的屬性
10

>>>del FoodMaterial.price # 如果這時我們將類的屬性也刪除掉
>>>print(tomato.price) # 無法調用屬性,所以報錯了
AttributeError: 'FoodMaterial' object has no attribute 'price'
7.關于self
  • self是類和對象中的特一個特殊的參數,代表類本身。
  • self只在類和對象中存在。
  • self不需要賦值。
  • self是類中默認的第一個參數。
  • 其實self并不是系統指定的關鍵字,他可以叫任何名字,只要他是類中的第一個參數就可以。
    1)在類中使用self參數
  • 很少直接使用類中的self參數
>>>class FoodMaterial():
>>>    def cut(self) :
>>>        print('我被切成片了!')
>>>FoodMaterial.cut(FoodMaterial) # 這時需要將類自身作為參數返回,不然會報錯。
我被切成片了!
  • 因為對象也屬于類,所以可以用對象作為self參數。但是這種用法比較奇葩,不推薦使用。
>>>class FoodMaterial():
>>>    def cut(self) :
>>>        print('我被切成片了!')
>>>tomato = FoodMaterial()
>>>FoodMaterial.cut(tomato) # 這里將方法當做參數代表類本身,也可以正常運行
我被切成片了!
  • 如果不使用self名稱,也沒有任何區別,但不推薦這樣使用。
>>>class FoodMaterial():
>>>    def cut(somethingbutnotself) : # 證明self并不是關鍵字
>>>        print('我被切成片了!')
>>>FoodMaterial.cut(FoodMaterial) 
我被切成片了!

2)在對象中使用self參數

  • 在調用對象方法時,會默認將自身作為第一個參數返回。所以如例(3.2)中,如果在沒有為方法添加self參數,而直接用對象中的該方法會報錯。
>>>class FoodMaterial():
>>>    def cut() :
>>>        print('我被切成片了!')
>>>tomato = FoodMaterial()
>>>tomato.cut() # 這里會提示傳入了一個參數,但是cut()不需要傳入參數
TypeError: cut() takes 0 positional arguments but 1 was given

>>># 正確的調用方式
>>>class FoodMaterial():
>>>    def cut(self) :
>>>        print('我被切成片了!')
>>>tomato = FoodMaterial()
>>>tomato.cut() # tomato會默認將自身作為第一個參數傳入方法
我被切成片了!
  • 在對象中self改變了變量和函數的作用域,成為對象內的全局變量。
>>>class FoodMaterial():
>>>    def __init__(self): # __init__是魔法函數,這里可以先理解為一個在對象創建時自動執行的方法。
>>>        self.price = 10 # 這里self.price成為對象內的全局變量
>>>    def getPrice(self) :
>>>        print('我的價格是:{price}'.format(price=self.price)) # 調用了方法內的變量self.price,證明其作用域是全局
>>>tomato = FoodMaterial() 
>>>tomato.getPrice()
我的價格是:10
8.Python中的構造函數
  • 構造函數是一種特殊的方法,用來在創建對象時為對象賦值。
  • Python中構造函數的方法是: def __init__(self,arg1,arg2)
  • 構造函數只能調用一次,就是在創建類的時候調用
  • __init__ 其實是一個魔法函數,后面會介紹。
>>>class FoodMaterial():
>>>    def __init__(self,price,colour): # 構造函數,參數在創建類時需要提供
>>>          self.price = price 
>>>          self.colour = colour
>>>          self.show() # 在創建類時就要執行的方法
>>>    def show(self):
>>>          print(self.price)
>>>          print(self.colour)
>>>tomato = FoodMaterial(price=10,colour='red') # 這里并沒有再專門調用show(),在創建時就自動執行了。
10
red

>>>print(FoodMaterial.price) # 直接用類訪問不到構造函數中的成員,因為構造函數只在創建對象時執行。
AttributeError: type object 'FoodMaterial' has no attribute 'price'

六、面向對象的三大特征

  • 封裝
  • 繼承
  • 多態
1. 封裝
  • 封裝就是對對象的成員進行訪問限制,是為了保護私隱,明確區分內外。
  • 封裝的級別分為:公開(public)、受保護的(protected)、私有的(private)
  • 與Java不同,Python中沒有public,private,protected的關鍵字
  • Python中的封裝并不是嚴格的語法,更像是一種規范。

1)公開的(public)
公共的封裝實際對成員沒有任何操作,任何地方都可以訪問。
2)受保護的(protected)

  • 受保護的成員名稱前增加一個下劃線_。
  • 受保護的封裝在類中或者子類中都可以訪問,但在外部不允許訪問。
  • 我測試受保護的成員實際上是可以直接訪問的,應該只是一種書寫規范,希望有人可以指正我。
_<受保護的成員名>
>>>class FoodMaterial():
>>>    _price = 10 # 受保護的屬性
>>>    def _getPrice(self) : # 受保護的方法
>>>        print('我的價格是:{price}'.format(price=self.price)) 

3)私有的(private)

  • 私有員名稱前增加兩個劃線__。
  • 私有成員只能在當前類或對象中訪問。
  • Python中的私有實現方式是一種被稱為name mangling的障眼法。
__<私有的成員名>
>>>class FoodMaterial():
>>>    __price = 10 # 私有的屬性
>>>    def __getPrice(self) : # 私有的方法
>>>        print('我的價格是:{price}'.format(price=self.__price)) 
>>>tomato = FoodMaterial() # 實例化
>>>print(tomato.__price) # 注意,這里的報錯是:不存在這個值!。
AttributeError: 'FoodMaterial' object has no attribute '__price'
>>>tomato.__getPrice()
AttributeError: 'FoodMaterial' object has no attribute '__getPrice'
  • 用魔法函數__dict__查看類的全部成員,可以看出私有成員實際是被改名為:_<類名>__<成員名>
>>>print(FoodMaterial.__dict__)
>>>mappingproxy({'__module__': '__main__',
>>>              '_FoodMaterial__price': 10, # 這里可以看到被改名了
>>>              '_FoodMaterial__getPrice': <function __main__.FoodMaterial.__getPrice(self)>,
>>>              '__dict__': <attribute '__dict__' of 'FoodMaterial' objects>,
>>>              '__weakref__': <attribute '__weakref__' of 'FoodMaterial' objects>,
>>>              '__doc__': None})

>>>print(tomato._FoodMaterial__price) # 試著訪問改名后的成員
10
>>>print(tomato._FoodMaterial__getPrice())
我的價格是:10
2. 繼承

1)繼承的作用:

  • 繼承就是一個類可以獲得另外一個類中的成員屬性和成員方法。
  • 繼承可以減少代碼,增加代碼的復用功。
  • 繼承可以建立類與類直接的關系。

2)繼承與被繼承的概念:

  • 被繼承的類叫父類,也叫基類,也叫超類。
  • 用于繼承的類,叫子類,也叫派生類。
  • 所有的類全都是object的子類,繼承時如果不特別指定父類,將默認繼承object類。

3)繼承的語法

class <子類名>(<父類名>):
         <類的內容> 
>>># 父類
>>>class Dog(object): # 這里不寫object也會默認繼承object
>>>    type = 'dog'
>>>    legs = 4
>>>    def bark(self):
>>>         print('汪汪!')

>>># 子類
>>>class Schnauzer(Dog): # schnauzer繼承了dog類
>>>     sub_type = 'schnauzer'

4)繼承的特性

  • 子類一旦繼承父類,則可以使用父類中除私有成員外的所有內容。
  • 子類繼承父類后并沒有將父類成員完全賦值到子類中,而是通過引用關系訪問調用。
  • 子類中可以定義獨有的成員屬性和方法。
  • 子類中定義的成員和父類成員如果相同,則優先使用子類成員。
>>># 父類
>>>class Dog(object): # 這里不寫object也會默認繼承object
>>>    type = 'dog'
>>>    def bark(self):
>>>         print('汪汪!')
>>>    def __showLegs(self): # 私有成員不會被繼承
>>>         print(4)

>>># 子類
>>>class Schnauzer(Dog): # schnauzer繼承了dog類
>>>     sub_type = 'schnauzer'
>>>     def bark(self): # 這里覆蓋了父類的方法
>>>          print('嗷嗷1')
>>>qq = Schnauzer()

>>>print(qq.type) # 繼承了父類的屬性
'dog'

>>>print(qq.bark()) # 同名時首先引用了自己的方法
嗷嗷!

>>>print(qq.sub_type) # 有了自己的屬性
'schnauzer'

>>>print(qq.__showLegs()) # 私有變量不會被繼承
AttributeError: 'schnauzer' object has no attribute '__showLegs'

5)super函數

  • super()函數用于調用父類的一個方法。
  • super().<父類方法名>() = <父類名>.<父類方法名>()
self(<父類名>,self).<方法名>(參數) # 意思是將父類的方法轉換為自己的方法。
self().<方法名>(參數) # python3.0中可以省略self()中的內容,但多重繼承中還是需要。
>>># 父類
>>>class Dog(): 
>>>    def bark(self):
>>>         print('汪汪!')

>>># 子類
>>>class Schnauzer(Dog): # schnauzer繼承了dog類
>>>    def barkTwice(self): 
>>>         super().bark() # 這里調用了父類的方法
>>>         print('嗷嗷!')
>>>qq = Schnauzer()
>>>qq.barkTwice()
汪汪!
嗷嗷!

6)繼承的順序

  • 優先查找自己的函數
  • 沒有則查找父類
  • 如果父類沒有則查找父類的父類
>>># 父類
>>>class Dog(): 
>>>    type = 'dog'

>>># 子類
>>>class Schnauzer(Dog): 
>>>    pass

>>># 孫類
>>>class BabySchnauzer(Schnauzer): 
>>>    pass

>>>qq = BabySchnauzer()
>>>print(qq.type) # 繼承了祖父類的屬性s
'dog'

7)繼承構造函數

  • 如果定義了構造函數,則實例化時使用構造函數。
  • 如果沒定義,則自動查找父類構造函數。
  • 如果重寫了構造函數,還要繼承父類的構造方法,可以使用 super()方法。
>>># 父類
>>>class Dog(): 
>>>    def __init__(self,legs):
>>>        self.type = 'dog'
>>>    def fatherType(self):
>>>        print('father type is {type}'.format(type=self.type))

>>># 子類
>>>class Schnauzer(Dog): 
>>>     def __init__(self,legs):
>>>         super().__init__(self,legs) # 這里調用了父類的構造函數,將參數傳入
>>>     def sonType(self):
>>>         print('son type is {type}'.format(type=self.type))

>>>qq = Schnauzer(4)
>>>qq.sonType()
son type is dog
>>>qq.fatherType() # 將參數傳給父類的方法,并調用方法
father type is dog

8)多繼承

  • 多繼承就是子類同時繼承多個類,方法是:def <子類名>(<父類一>,<父類二>):
  • 如果兩個父類的屬性相同,則繼承寫在前面的父類。
  • 可以通過inspect.getmro(<類名>)查看類的MRO列表(繼承順序)
>>># 父類一·
>>>class Human():
>>>    type = 'human'
>>>    def introHuman(self):
>>>          print('i am human!')

>>>class Fish():
>>>    type = 'fish'
>>>    def introFish(self):
>>>          print('i am fish!')

>>>class Fishman(human,fish): # 多個父類
>>>    pass
>>>f = Fishman()

>>>print(f.introHuman()) # 同時繼承了兩個父類的方法
i am human!
>>>print(f.introFish())
i am fish!

>>>print(f.type) # 屬性名相同則繼承前面的父類
human

>>>import inspect # 導入inspect包
>>>inspect.getmro(fishman) # 查看fishman類的mro
(__main__.fishman, __main__.human, __main__.fish, object)
  • 單繼承和多繼承的對比:
/ 單繼承 多繼承
優點 傳承有序邏輯清晰語法簡單隱患少 類的功能擴展方便
缺點 功能擴展性受父類限制 繼承關系混亂
  • 經典鉆石繼承問題:當一個孫類同時繼承多個父類,而這些父類又繼承同一個祖父類時,如果孫類同時繼承多個父類的構造函數,祖父類構造函數會被初始化多次。
# 祖父類
>>>class Grandpa(object):
>>>    def __init__(self):
>>>        print('from grandpa')
# 父類1
>>>class Dad1(Grandpa):
>>>    def __init__(self):
>>>        grandpa.__init__(self)
>>>        print('from dad1')
# 父類2
>>>class Dad2(Grandpa):
>>>    def __init__(self):
>>>        grandpa.__init__(self)
>>>        print('from dad2')
# 子類
>>>class Son(Dad1, Dad2):
>>>    def __init__(self):
>>>        dad1.__init__(self) 
>>>        dad2.__init__(self) # 同時調用兩個父類的構造函數
>>>        print('from son')

>>>s = Son()
from grandpa
from dad1
from grandpa
from dad2
from son
  • 鉆石繼承問題解決方案:使用super()函數
# 祖父類
>>>class Grandpa(object):
>>>    def __init__(self):
>>>        print('from grandpa')
# 父類1
>>>class Dad1(Grandpa):
>>>    def __init__(self):
>>>        super().__init__()
>>>        print('from dad1')
# 父類2
>>>class Dad2(Grandpa):
>>>    def __init__(self):
>>>        super().__init__()
>>>        print('from dad2')
# 子類
>>>class Son(dad1, dad2):
>>>    def __init__(self):
>>>        super().__init__() #這里同時繼承了兩個父類的構造函數
>>>        print('from son')

>>>s = Son() # 這里祖父類只調用了一次
from grandpa
from dad1
from dad2
from son
3. 多態

1)理解多態

  • 首先要理解創建一個類就是創建了一種數據類型,和list,dict是一樣的。
>>>a = list()
>>>type(a) 
list
>>>isinstance(a,list) # a 屬于list類型
True

>>>class Animal():
>>>    pass
>>>dog = Animal()
>>>type(dog)
__main__.Animal
>>>isinstance(dog,Animal)  # dog是animal數據類型
True
  • 多態是指同一個對象在不同情況下有不同的狀態出現。
  • 多態是一種思想,而不是一種語法。
  • 多態增加了程序的靈活性和擴展性。
>>>class Animal(): # 創造動物的數據類型
>>>    pass

>>>class Dog(Animal): # 狗屬于動物
>>>    pass

>>>class Cat(Animal): # 貓也屬于動物
>>>    pass

>>>qq = Dog()
>>>isinstance(qq,Dog)
True
>>>isinstance(qq,Animal) # qq即屬于狗,也屬于動物
True

>>>mimi = Cat()
>>>isinstance(mimi,Cat)
True
>>>isinstance(mimi,Animal) # mimi即屬于貓,也屬于動物
True

2)多態性

  • 指具有不同功能的函數可以使用相同的函數名,這樣就可以用一個函數名調用不同內容的函數。
  • 多態性是一鐘調用方式,
>>>class Animal():
>>>    def talk(self):
>>>        pass

>>>class Human():
>>>    def talk(self):
>>>        print('你好!')

>>>class Dog():
>>>    def talk(self):
>>>        print('汪汪!')

>>>class Cat():
>>>    def talk(self):
>>>        print('喵喵!')

>>> ppl = Human()
>>> pp = Dog()
>>> mimi = Cat()

>>> Human.talk()
你好!
>>> Dog.talk()
汪汪!
>>> Cat.talk()
喵喵!

3)mixin模式

  • mixin模式通過多繼承來實現,子類通過繼承Mixin類獲得一種功能。
  • Mixin類用<類名 + Mixin>的方式起名,但這不是語法,而是告訴后來讀代碼的人,這是一個Mixin類。
  • 一個Mixin類可以理解為一種功能,且只能是一種功能,如果有多個功能則創建多個Mixin類。
  • Mixin不能依賴于子類的實現
  • 子類及時沒有繼承這個Mixin類, 也能照樣工作,只是缺少了某個功能
>>>class Animal():
>>>    pass

>>>class FlyMixin():  # Mixin類
>>>    def fly(self):
>>>        print('I can fly!')

>>>class Bird(Animal,FlyMixin): # 鳥既是動物,也能飛
>>>    pass

>>>kiki = Bird() # kiki作為bird類,獲得了fly能力
>>>kiki.fly()
I can fly!

4)mixin模式的優點

  • 使用Mixin可以在不對類進行任何修改的情況下,擴充功能
  • 可以方便的組織和維護不同功能組件的劃分
  • 可以根據需要任意調整功能類的組合
  • 可以避免創建很多新的類,導致類的繼承混亂

七.類的成員描述符

(這里如果暫時看不懂請先看下一章魔法函數)

  • 類的成員描述符是為了在類中,對類的成員屬性進行相關操作而創建的一種方式,大部分屬于數據清洗。
  • 屬性有三種操作:get獲取屬性值、set修改或添加屬性、delete刪除屬性。
  • 實現成員描述符包括三種方法:描述器、屬性修飾符、property函數
1. 描述器
  • 一個類只要實現了__get__,__set__,__delete__中任意一個方法,我們就可以叫它描述器
  • 如果僅僅實現了__get__.就是非數據描述器 non-data descriptor
  • 同時實現了__get__,__set__,就是數據描述器 data descriptor

1)__get__
不管是類還是實例去訪問,默認都獲得的是__get__的返回值,但是,如果中間有任何一次重新賦值,那么,這個實例獲得的是新的值(對象),已經和原來的描述器完全脫離了關系。

>>>class Tomato():
>>>   def __get__(self,obj,cls):
>>>       print('here inside __get__ !')

>>>class MyClass():
>>>    t = Tomato() #類或實例默認獲得__get__的返回值
>>>m = MyClass()
>>>m.t # 調用了__get__
here inside __get__ !

2)__set__
后期通過實例對描述器進行賦值,那么訪問的是__set__,并且永遠關聯起來。但是如果通過修改類屬性的方式復制,那么也會被重新獲取新的值(對象)。

>>>class Tomato():
>>>   def __set__(self,obj,cls):
>>>       print('here inside __set__ !')

>>>class MyClass():
>>>    t = Tomato() #類或實例默認獲得__get__的返回值
>>>m = MyClass()
>>>m.t = 'hello' # 賦值時調用__set__
here inside __set__ !

3)__delete__
采用del刪除屬性時,觸發__delete__。

>>>class Tomato():
>>>   def __delete__(self,obj):
>>>       print('here inside __delete__ !')

>>>class MyClass():
>>>    t = Tomato()
>>>m = MyClass()
>>>del m.t # 用del刪除屬性時,觸發__delete__
here inside __delete__ !
2. 屬性修飾符
  • 屬性修飾符包括:__getattribute__()、__getattr__()、__setattr__()、__delattr__()

1)__getattribute__()
調用屬性或方法時,會先強制調用 getattribute 方法,然后再傳回屬性的值或者是 函數(方法)的引用。

>>>class Tomato():
>>>    def __init__(self):
>>>        name = 'tomato'
>>>    def __getattribute__(self, item):
>>>        print('inside __getattribute__!')
>>>t = Tomato()
>>>t.name # 調用屬性時觸發__getattribute__
inside __getattribute__!

2)__getattr__()
在實例以及對應的__dict__中查找屬性失敗,那么會調用__getattr__函數

>>>class Tomato():
>>>    def __getattr__(self, item):
>>>        print('inside __getattr__!')
>>>t = Tomato()
>>>t.name # 這里name并不存在,所以觸發__getattr__
inside __getattr__!

3)__setattr__()
當為類屬性賦值時,調用__setattr__()語句

>>>class Tomato():  
>>>    def __setattr__(self, item,value):
>>>        print('inside __setattr__!')
>>>t = Tomato()
>>>t.name = 'tomato' # 雖然和上個例子看起來相似,但其實這里時因為賦值觸發__setattr__
inside __setattr__!

4)__delattr__()
只要使用del語句刪除屬性,就會調用這個方法

>>>class Tomato():
>>>   def __init__(self):
>>>        self.name = 'tomato'
>>>   def __delattr__(self, item):
>>>        print('inside __delattr__!')
>>>t = Tomato()
>>>del t.name # 用del刪除元素時觸發__delattr__
inside __delattr__!
3. property函數
  • property() 函數的作用是在新式類中返回屬性值。
  • 使用語法 property(<get方法>,<set方法>,<del方法>,<屬性描述>)
  • get、set和del方法對應描述器的三個方法。
  • 三個方法的名字可以任意取,但是在property()中要按順序一一對應。
>>>class Tomato():
>>>    def __init__(self):
>>>        self._name = 'tomato' # 創建protected屬性
>>>    def get_name(self): # 創建屬性的get方法
>>>        print('here inside get_name')
>>>        return self._name
>>>    def set_name(self, value): # 創建屬性的set方法
>>>        print('here inside set_name')
>>>        self._name = value
>>>    def del_name(self): # 創建屬性的del方法
>>>        print('here inside del_name')
>>>        del self._name
>>>    name = property(get_name, set_name, del_name, "I'm the 'name' property.") # 為name屬性附加property
>>>t = Tomato()

>>>print(t.name) # 觸發get_name()
here inside get_name
'tomato'

>>>t.name = 'Tomato' # 觸發set_name()
here inside set_name

>>>del t.name # 觸發del_name()
here inside del_name

>>>help(t.name) # 觸發描述
here inside get_name
  • 用裝飾器實現property(如果對裝飾器不熟悉可以先跳過):
>>>class Tomato():
>>>    def __init__(self):
>>>        self._name = 'tomato' # 創建protected屬性
>>>    @property # 相當于get
>>>    def name(self): # 創建屬性的get方法
>>>        print('here inside get_name')
>>>        return self._name
>>>    @name.setter # 相當于set
>>>    def set_name(self, value): # 創建屬性的set方法
>>>        print('here inside set_name')
>>>        self._name = value
>>>    @name.deleter # 相當于del
>>>    def del_name(self): # 創建屬性的del方法
>>>        print('here inside del_name')
>>>        del self._name
>>>t = Tomato()

>>>print(t.name) # 觸發get_name()
here inside get_name
'tomato'

>>>t.name = 'Tomato' # 觸發set_name()
here inside set_name

>>>del t.name # 觸發del_name()
here inside del_name

>>>help(t.name) # 觸發描述
here inside get_name

八.類的內置屬性

類包含以下默認內置屬性:
1)__dict__:以字典的方式顯示類的成員組成

>>>class FoodMaterial():
>>>    def __init__(self):
>>>        self._price = 10 # 受保護的屬性
>>>    def getPrice(self) : # 受保護的方法
>>>        print('我的價格是:{price}'.format(price=self.price)) 
>>>print(FoodMaterial.__dict__)
{'__module__': '__main__', '__init__': <function FoodMaterial.__init__ at 0x000001A9534477B8>, 'getPrice': <function FoodMaterial.getPrice at 0x000001A953447840>, '__dict__': <attribute '__dict__' of 'FoodMaterial' objects>, '__weakref__': <attribute '__weakref__' of 'FoodMaterial' objects>, '__doc__': None}

>>>tomato = FoodMaterial()
>>>print(tomato.__dict__)
{'_price': 10}

2)__doc__: 獲取類的文檔信息

>>>class FoodMaterial():
>>>    '''This is class FoodMaterial'''
>>>    pass
>>>print(FoodMaterial.__doc__)
This is class FoodMaterial

>>>tomato = FoodMaterial()
>>>print(tomato.__doc__)
This is class FoodMaterial

3)__name__:獲取類的名稱,如果在模塊中使用,獲取模塊的名稱

>>>class FoodMaterial():
>>>    pass
>>>print(FoodMaterial.__name__)
'FoodMaterial'

4)__bases__: 獲取某個類的所有父類,以元組的方式顯示

>>>class Animal():
>>>    pass

>>>class Baby():
>>>    pass

>>>class Dog(Animal):
>>>    pass

>>>class puppy(Dog,Baby):
>>>    pass

>>>print(puppy.__bases___)
(__main__.Dog, __main__.Baby)

九.魔法函數

1)__init__
見:構造函數 - 5.8
2)__str__
當類被打印時,將調用str方法的值。

>>>class Dog(object): # 如果不處理__str__方法
>>>    pass
>>>dog = Dog()
>>>print(dog) 
<__main__.Dog object at 0x000001A953477240>

>>>class Dog(object):
>>>    def __str__(self):
>>>        return 'It is a dog!'
>>>dog = Dog()
>>>print(dog) # 返回了__str__方法的值
It is a dog!

3)__new__

  • 初始化對象,在__init__前執行
  • __new__方法包含一個參數,用于傳入要實例化的對象
  • __new__方法必須返回一個值,是實例化后的對象
>>>class Dog(object):
>>>    def __new__(cls):
>>>        print(cls)
>>>        return 1
>>>dog = Dog() # 調用了print(cls)
<class '__main__.Dog'>

>>>print(dog) # 由于返回值是1,所以Dog變成了1
1
>>>type(dog) # 類型也變成了整數
int

4)__call__

  • 定義后可以將對象當作函數使用。
>>>class Dog(object):
>>>    def __call__(self,name):
>>>        print('{name} is running!'.format(name=name))
>>>dog = Dog()
>>>dog('qq') # 調用__call__
qq is running!

5)__len__

  • 在用len()方法時,會調用__len__方法
>>>class Dog(object):
>>>    def __len__(self):
>>>        print('very very long!')
>>>dog = Dog()
>>>len(dog)
very very long!
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-100-b3792c81c856> in <module>
----> 1 len(dog)

TypeError: 'NoneType' object cannot be interpreted as an integer

6)__repr__
函數str() 用于將值轉化為適于人閱讀的形式,而repr() 轉化為供解釋器讀取的形式,某對象沒有適于人閱讀的解釋形式的話,str() 會返回與repr(),所以print展示的都是str的格式。

>>>class Dog(object):
>>>    def __repr__(self):
>>>        return 'It is a dog!'
>>>dog = Dog()
>>>print(dog) # 返回了__str__方法的值
It is a dog!

7)__setattr__
見:類的成員描述符 - 7
8)__getattr__
見:類的成員描述符 - 7
9)__delattr__
見:類的成員描述符 - 7
10)__getattribute__
見:類的成員描述符 - 7
11)__del__
見:類的成員描述符 - 7
12)__getitem__
定義__getitem__方法,就可以用對象像字典一樣取值:key[value]

>>>class Dog(object):
>>>    def __init__(self):
>>>        self.name = 'dog'
>>>    def __getitem__(self,key):
>>>        print('here in __getitem__')
>>>        return self.name
>>>dog = Dog()
>>>print(dog['name']) # 調用了__getitem__方法
here in __getitem__
'dog'

13)__delitem__
定義__delitem__方法,就可以用對象像字典一樣刪除值:del key[value]

>>>class Dog(object):
>>>    def __init__(self):
>>>        self.name = 'dog'
>>>    def __delitem__(self,key):
>>>        print('here in __delitem__')
>>>        del self.name
>>>dog = Dog()
>>>del dog['name'] # 調用了__delitem__方法
here in __delitem__

>>>print(dog.name)
AttributeError: 'Dog' object has no attribute 'name'

十.抽象類

  • 抽象類就是沒有具體實現內容的方法成為抽象類
  • 抽象類的主要意義是規范了子類的行為和接口
  • 抽象類的使用需要借助abc模塊
  • 抽象類不允許直接實例化
  • 必須繼承才可以使用,且繼承的子類必須實現所有繼承來的抽象方法
  • 假定子類沒有是現實所有繼承的抽象方法,則子類也不能實例化
>>>import abc # 導入abc模塊
>>>class Animal(metaclass=abc.ABCMeta):
>>>    type = animal
>>>    @abc.abstractmethod # 定義抽象方法,無需實現功能
>>>    def bart(self):
>>>        pass
>>>dog = Animal() # 抽象類不能被實例化
TypeError: Can't instantiate abstract class animal with abstract methods bart

>>>class Dog(animal):  # 通過繼承實現抽象類
>>>    def bart(self):
>>>        print('嗷嗷!')
>>>dog = Dog()
>>>dog.bart()
嗷嗷!

參考資料



本文作者:大師兄(superkmi)


夢幻微武俠
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容