本章內容: 創建類和對象 面向對象三大特性(封裝、繼承、多態) 類的成員(欄位、方法、屬性) 類成員的修飾符(公有、私有) 類的特殊成員 面向對象編程是一種編程方式,此編程方式的落地需要使用 “類” 和 “對象” 來實現,所以,面向對象編程其實就是對 “類” 和 “對象” 的使用。 類就是一個模板, ...
本章內容:
- 創建類和對象
- 面向對象三大特性(封裝、繼承、多態)
- 類的成員(欄位、方法、屬性)
- 類成員的修飾符(公有、私有)
- 類的特殊成員
創建類和對象 |
面向對象編程是一種編程方式,此編程方式的落地需要使用 “類” 和 “對象” 來實現,所以,面向對象編程其實就是對 “類” 和 “對象” 的使用。
類就是一個模板,模板里可以包含多個函數,函數里實現一些功能
對象則是根據模板創建的實例,通過實例對象可以執行類中的函數
- class是關鍵字,表示類
- 創建對象,類名稱後加括弧即可
# 創建類 class Foo: def buy(self): print("This is buy.") def Hello(self, name): print("This is hello.") # 根據類Foo創建對象obj obj = Foo() obj.buy() #執行Bar方法 obj.Hello('nick') #執行Hello方法
類和對象在記憶體中是如何保存的?
類以及類中的方法在記憶體中只有一份,而根據類創建的每一個對象都在記憶體中需要存一份,大致如下圖:
如上圖所示,根據類創建對象時,對象中除了封裝 name 和 age 的值之外,還會保存一個類對象指針,該值指向當前對象的類。
當通過 obj1 執行方法時,過程如下:
- 根據當前對象中的 類對象指針 找到類中的方法
- 將對象 obj1 當作參數傳給 方法的第一個參數 self
註:Java和C#來說只支持面向對象編程,而python比較靈活即支持面向對象編程也支持函數式編程
面向對象三大特性 |
面向對象的三大特性是指:封裝、繼承和多態。
一、封裝
封裝,顧名思義就是將內容封裝到某個地方,以後再去調用被封裝在某處的內容。
所以,在使用面向對象的封裝特性時,需要:
- 將內容封裝到某處
- 從某處調用被封裝的內容
1、將內容封裝到某處
self 是一個形式參數,當執行 obj = Foo('nick', 18 ) 時,self 等於 obj
當執行 obj2 = Foo('jenny', 21 ) 時,self 等於 obj2
2、從某處調用被封裝的內容
調用被封裝的內容時,有兩種情況:
- 通過對象直接調用
- 通過self間接調用
class Foo: def __init__(self, name, age): self.name = name self.age = age obj = Foo('nick', 18) print obj.name # 直接調用obj對象的name屬性 print obj.age # 直接調用obj對象的age屬性 obj2 = Foo('jenny', 21) print obj2.name # 直接調用obj2對象的name屬性 print obj2.age # 直接調用obj2對象的age屬性
class Foo: def __init__(self, name, age): self.name = name self.age = age def detail(self): print self.name print self.age obj = Foo('nick', 18) obj.detail() # Python預設會將obj傳給self參數,即:obj.detail(obj),所以,此時方法內部的 self = obj,即:self.name 是 nick ;self.age 是 18 obj2 = Foo('jenny', 21) obj2.detail() # Python預設會將obj2傳給self參數,即:obj1.detail(obj2),所以,此時方法內部的 self = obj2,即:self.name 是 jenny ; self.age 是 21
#封裝 #非主流方式 class Foo: def fetch(self): print(self.nick) def add(self): print(self.jenny) obj = Foo() obj.nick = "Nick_cool" obj.fetch() # obj2 = Foo() obj.nick = "Nick_cool_2" obj.fetch() obj1 = Foo() obj1.jenny = "Jenny_nice" obj1.add() #封裝 class Foo: def __init__(self,bk): """ 構造方法 """ #析構方法在垃圾回收是解釋器自己調用 self.name = bk self.job = "pythoner" # obj.job = "pythoner" self.age = 18 # obj.age = 18 def fetch(self): print(self.name) print(self.age) print(self.job) obj = Foo("nick") obj.fetch()
綜上所述,對於面向對象的封裝來說,其實就是使用構造方法將內容封裝到 對象 中,然後通過對象直接或者self間接獲取被封裝的內容。
二、繼承
對於面向對象的繼承來說,其實就是將多個類共有的方法提取到父類中,子類僅需繼承父類而不必一一實現每個方法。
註:除了子類和父類的稱謂,你可能看到過 派生類 和 基類 ,他們與子類和父類只是叫法不同而已。
# 繼承 # 基類 class Animals: def __init__(self,name): self.name = name def eat(self): print(self.name,"吃") # 派生類 class dog(Animals): def tell(self): print("汪星人") dog = dog("啊黃") dog.tell() dog.eat()
繼承 __init__
派生類預設不繼承基類__init__,需要用super聲明
class A: def __init__(self): self.name = "nick" class B(A): def __init__(self): self.age = 18 super(B, self).__init__() #super首先找到B的父類A,然後把類B的對象self轉換為類A的對象,然後“被轉換”的類A對象調用自己的__init__函數 # A.__init__(self) #指定運行A中__init__,不推薦 obj = B() print(obj.__dict__)
多繼承:
Python的類可以繼承多個類,Java和C#中則只能繼承一個類
Python3的類繼承多個類的尋找方法的方式,Python 3中沒有經典類、新式類之分
# 多繼承 class A: def f1(self): print("A") class B(A): def f(self): print("B") class C(A): def f(self): print("C") class D(B): def f(self): print("D") class E(C): def f1(self): print("E") class F(D,E): def f(self): print("F") f1 = F() f1.f1()
Python2的類如果繼承了多個類,那麼其尋找方法的方式有兩種,分別是:深度優先和廣度優先
- 當類是經典類時,多繼承情況下,會按照深度優先方式查找
- 當類是新式類時,多繼承情況下,會按照廣度優先方式查找
經典類和新式類,從字面上可以看出一個老一個新,新的必然包含了跟多的功能,也是之後推薦的寫法,從寫法上區分的話,如果 當前類或者父類繼承了object類,那麼該類便是新式類,否則便是經典類。
class D: def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 執行bar方法時 # 首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麽有,則繼續去D類中找,如果D類中麽有,則繼續去C類中找,如果還是未找到,則報錯 # 所以,查找順序:A --> B --> D --> C # 在上述查找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了 a.bar()經典類多繼承
class D(object): def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 執行bar方法時 # 首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麽有,則繼續去C類中找,如果C類中麽有,則繼續去D類中找,如果還是未找到,則報錯 # 所以,查找順序:A --> B --> C --> D # 在上述查找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了 a.bar()新式類多繼承
經典類:首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麽有,則繼續去D類中找,如果D類中麽有,則繼續去C類中找,如果還是未找到,則報錯
新式類:首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麽有,則繼續去C類中找,如果C類中麽有,則繼續去D類中找,如果還是未找到,則報錯
註意:在上述查找過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
函數方法里調用函數方法執行順序
# 函數方法里調用函數方法執行順序 class D: def buy(self): self.f1() #調用 f1() def f1(self): print("This is D f1.") class C(D): def f1(self): print("This is C f1.") class B: def f1(self): print("This is B f1.") class A(B, C): pass obj = A() obj.buy()
三、多態
多態性(polymorphisn)是允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。 那麼,多態的作用是什麼呢?我們知道,封裝可以隱藏實現細節,使得代碼模塊化;繼承可以擴展已存在的代碼模塊(類);它們的目的都是為了——代碼重用。而多態則是為了實現另一個目的——介面重用!多態的作用,就是為了類在繼承和派生的時候,保證使用“家譜”中任一類的實例的某一屬性時的正確調用。 Pyhon不支持多態並且也用不到多態,多態的概念是應用於Java和C#這一類強類型語言中,而Python崇尚“鴨子類型”。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.showPython偽代碼實現Java或C#的多態
class F1: pass class S1(F1): def show(self): print 'S1.show' class S2(F1): def show(self): print 'S2.show' def Func(obj): print obj.show() s1_obj = S1() Func(s1_obj) s2_obj = S2() Func(s2_obj)Python “鴨子類型”
class Animal: def __init__(self, name): # Constructor of the class self.name = name def talk(self): # Abstract method, defined by convention only raise NotImplementedError("Subclass must implement abstract method") class Cat(Animal): def talk(self): return 'Meow!' class Dog(Animal): def talk(self): return 'Woof! Woof!' animals = [Cat('Missy'), Dog('Lassie')] for animal in animals: print animal.name + ': ' + animal.talk()通過Python模擬的多態
類的方法 |
類的成員可以分為三大類:欄位、方法和屬性。
註:所有成員中,只有普通欄位的內容保存對象中,即:根據此類創建了多少對象,在記憶體中就有多少個普通欄位。而其他的成員,則都是保存在類中,即:無論對象的多少,在記憶體中只創建一份。
一、欄位
欄位包括:普通欄位和靜態欄位,他們在定義和使用中有所區別,而最本質的區別是記憶體中保存的位置不同,
- 普通欄位屬於對象
- 靜態欄位屬於類
class Foo: # 靜態欄位 country = "China" def __init__(self, name): # 普通欄位 self.name = name # 直接訪問靜態欄位 Foo.country # 直接訪問普通欄位 obj = Foo("山西")
由上述代碼可以看出【普通欄位需要通過對象來訪問】【靜態欄位通過類訪問】,在使用上可以看出普通欄位和靜態欄位的歸屬是不同的。
- 靜態欄位在記憶體中只保存一份
- 普通欄位在每個對象中都要保存一份
應用場景: 通過類創建對象時,如果每個對象都具有相同的欄位,那麼就使用靜態欄位
二、方法
方法包括:普通方法、靜態方法和類方法,三種方法在記憶體中都歸屬於類,區別在於調用方式不同。
- 普通方法:由對象調用;至少一個self參數;執行普通方法時,自動將調用該方法的對象賦值給self;
- 類方法:由類調用; 至少一個cls參數;執行類方法時,自動將調用該方法的類複製給cls;
- 靜態方法:由類調用;無預設參數;
class Foo: #靜態方法 @staticmethod def xo(arg1, arg2): #無預設參數,可不傳參數,可傳任意參數 print("xo") #類方法 @classmethod def xxoo(cls): #定義類方法,至少有一個cls參數 print(cls) #普通方法,類中 def show(self): #定義普通方法,至少有一個self參數 print("show") # 調用靜態方法 Foo.xo(1,2) # 調用類方法 Foo.xxoo() # 調用普通方法 obj = Foo: obj.show()
相同點:對於所有的方法而言,均屬於類(非對象)中,所以,在記憶體中也只保存一份。
不同點:方法調用者不同、調用方法時自動傳入的參數不同。
三、屬性
由屬性的定義和調用要註意一下幾點:
- 定義時,在普通方法的基礎上添加 @property 裝飾器;
- 定義時,屬性僅有一個self參數
- 調用時,無需括弧
方法:foo_obj.func()
屬性:foo_obj.prop
註意:屬性存在意義是:訪問屬性時可以製造出和訪問欄位完全相同的假象
屬性由方法變種而來,如果Python中沒有屬性,方法完全可以代替其功能。
class Foo: def __init__(self, name): self.name = name # 屬性,將方法偽造成一種欄位 @property def end(self): return self.name # 修改end值 @end.setter def end(self, new_name): self.name = new_name obj = Foo("nick") # 調用屬性,不需要加括弧 result2 = obj.end print(result2) # 調用修改end.setter屬性(自動將jenny傳入當參數new_name) obj.end = "jenny" result3 = obj.end print(result3)
類成員的修飾符 |
每一個類的成員都有兩種形式:
- 公有成員,在任何地方都能訪問
- 私有成員,只有在類的內部才能方法
私有成員和公有成員的定義不同:私有成員命名時,前兩個字元是下劃線。(特殊成員除外,例如:__init__、__call__、__dict__等)
class Foo: xo = "xo" #公有欄位 __ox = "ox" #私有欄位 def __init__(self): self.name = "nick" #公有欄位 self.__name2 = "nick" #私有欄位
私有成員和公有成員的訪問限制不同:
靜態欄位
- 公有靜態欄位:類可以訪問;類內部可以訪問;派生類中可以訪問
- 私有靜態欄位:僅類內部可以訪問;
class C: name = "公有靜態欄位" def func(self): print C.name class D(C): def show(self): print C.name C.name # 類訪問 obj = C() obj.func() # 類內部可以訪問 obj_son = D() obj_son.show() # 派生類中可以訪問公有靜態欄位
class C: __name = "公有靜態欄位" def func(self): print C.__name class D(C): def show(self): print C.__name C.__name # 類訪問 ==> 錯誤 obj = C() obj.func() # 類內部可以訪問 ==> 正確 obj_son = D() obj_son.show() # 派生類中可以訪問 ==> 錯誤私有靜態欄位
普通欄位
- 公有普通欄位:對象可以訪問;類內部可以訪問;派生類中可以訪問;
- 私有普通欄位:僅類內部可以訪問;
class C: def __init__(self): self.foo = "公有欄位" def func(self): print self.foo # 類內部訪問 class D(C): def show(self): print self.foo # 派生類中訪問 obj = C() obj.foo # 通過對象訪問 obj.func() # 類內部訪問 obj_son = D(); obj_son.show() # 派生類中訪問公有欄位
class C: def __init__(self): self.__foo = "私有欄位" def func(self): print self.foo # 類內部訪問 class D(C): def show(self): print self.foo # 派生類中訪問 obj = C() obj.__foo # 通過對象訪問 ==> 錯誤 obj.func() # 類內部訪問 ==> 正確 obj_son = D(); obj_son.show() # 派生類中訪問 ==> 錯誤私有欄位
方法、屬性的訪問於上述方式相似,即:私有成員只能在類內部使用
ps:如果想要強制訪問私有欄位,可以通過 【對象._類名__私有欄位明 】訪問(如:obj._C__foo),不建議強制訪問私有成員。
類的特殊成員 |
成員名前如果有兩個下劃線,則表示該成員是私有成員,私有成員只能由類內部調用。
1. __doc__
表示類的描述信息
class Foo: """ 描述類信息 """ def func(self): pass print Foo.__doc__ #輸出:類的描述信息__doc__
2. __module__ 和 __class__
__module__ 表示當前操作的對象在那個模塊
__class__ 表示當前操作的對象的類是什麼
#!/usr/bin/env python # -*- coding:utf-8 -*- class C: def __init__(self): self.name = 'nick'lib/aa.py
from lib.aa import C obj = C() print obj.__module__ # 輸出 lib.aa,即:輸出模塊 print obj.__class__ # 輸出 lib.aa.C,即:輸出類index.py
3. __init__
構造方法,通過類創建對象時,自動觸發執行。
class Foo: def __init__(self, name): self.name = name self.age = 18 obj = Foo('nick') # 自動執行類中的 __init__ 方法__init__
4. __del__
析構方法,當對象在記憶體中被釋放時,自動觸發執行。
註:此方法一般無須定義,因為Python是一門高級語言,程式員在使用時無需關心記憶體的分配和釋放,因為此工作都是交給Python解釋器來執行,所以,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。
class Foo: def __del__(self): pass__del__
5. __call__
對象後面加括弧,觸發執行。
註:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對於 __call__ 方法的執行是由對象後加括弧觸發的,即:對象() 或者 類()()
# __call__ class Foo: def __init__(self): print("This is init") def __call__(self, *args, **kwargs): print("This is call") return "CC" obj = Foo() # 執行 __init__ obj() # 執行 __call__ result = Foo()() # 執行 __call__ print(result)__call__
6. __dict__
類或對象中的所有成員
class Province: country = 'China' def __init__(self, name, count): self.name = name self.count = count def func(self, *args, **kwargs): print 'func' # 獲取類的成員,即:靜態欄位、方法、 print Province.__dict__ # 輸出:{'country': 'China', '__module__': '__main__', 'func': <function func at 0x10be30f50>, '__init__': <function __init__ at 0x10be30ed8>, '__doc__': None} obj1 = Province('shangxi',10000) print obj1.__dict__ # 獲取 對象obj1 的成員 # 輸出:{'count': 10000, 'name': 'shangxi'} obj2 = Province('shangdong', 3888) print obj2.__dict__ # 獲取 對象obj1 的成員 # 輸出:{'count': 3888, 'name': 'shangdong'}__dict__
7. __str__
如果一個類中定義了__str__方法,那麼在列印對象時,預設輸出該方法的返回值。
class Foo: def __str__(self): return 'nick' obj = Foo() print obj # 輸出:nick__str__
8、__getitem__、__setitem__、__delitem__
用於索引操作,如字典。以上分別表示獲取、設置、刪除數據
class Foo: def __getitem__(self, item): print(item) def __setitem__(self, key, value): print(key, value) def __delitem__(self, key): print(key) obj = Foo() obj["nick"] # 自動觸發執行 __getitem__ obj["nick"] = "jenny" # 自動觸發執行 __setitem__ del obj["nick"] # 自動觸發執行 __delitem____getitem____setitem____delitem__
9、__getslice__、__setslice__、__delslice__
該三個方法用於分片操作,如:列表
class Foo(object): def __getslice__(self, i, j): print '__getslice__',i,j def __setslice__(self, i, j, sequence): print '__setslice__',i,j def __delslice__(self, i, j): print '__delslice__',i,j obj = Foo() obj[-1:1] # 自動觸發執行 __getslice__ obj[0:1] = [11,22,33,44] # 自動觸發執行 __setslice__ del obj[0:2] # 自動觸發執行 __delslice__