面向對象的編程(object oriented programming),簡稱OOP:是一種編程的思想。OOP把對象當成一個程式的基本單元,一個對象包含了數據和操作數據的函數。面向對象的出現極大的提高了編程的效率,使其編程的重用性增高。 模擬場景理解面向對象和麵向過程: 1 ''' 2 使用面向過程 ...
面向對象的編程(object oriented programming),簡稱OOP:是一種編程的思想。OOP把對象當成一個程式的基本單元,一個對象包含了數據和操作數據的函數。面向對象的出現極大的提高了編程的效率,使其編程的重用性增高。
模擬場景理解面向對象和麵向過程:
1 ''' 2 使用面向過程的思想解決吃飯的問題? 3 步驟: 4 1).思考今天吃什麼? 5 2).去買菜(貨比三家) 6 3).摘菜 7 4).洗菜 8 5).切菜 9 6).炒菜 10 7).燜飯 11 8).吃飯 12 9).洗刷 13 使用面向對象的思想解決吃飯的問題? 14 步驟: 15 1).思考今天吃什麼? 16 2).去飯店 17 ①.調用服務員的點菜功能 18 ②.將菜品告知後臺大廚 19 ③.大廚調用服務員的上菜功能 20 3).開始吃飯 21 4).結賬走人(多種支付方式) 22 '''
Python是解釋性語言,但是它是面向對象的,能夠進行對象編程。面向對象是一種編程方式,此編程方式的實現是基於對 類 和 對象 的使用;類 是一個模板,模板中包裝了多個“函數”供使用(可以講多函數中公用的變數封裝到對象中);對象,根據模板創建的實例(即:對象),實例用於調用被包裝在類中的函數。大白話理解類和對象的區別:i類:具有一些列相同特征、行為的"事物",它的表現是不具體的、不清晰的、模糊的概念;對象:從類中實例化得到,一個實實在在的個體,在記憶體中有體現;它的表現是具體的、清晰的、看得見摸的著的。
遇到面向對象的問題,通常可以考慮如下三個環節:
1). 設計類,定義屬性、函數、...(可能需要花費大量的時間) ;
2). 創建(實例化)對象(簡單,一行代碼搞定,但是記憶體比較複雜);
3). 對象調用屬性或者函數完成需求。
1 # 1.設計類 2 class Car(object): 3 4 # 屬性 5 color = "紅色" 6 brand = "BMW" 7 number = "滬A88888" 8 9 # 函數/方法 10 def run(self): 11 print('%s的%s,車牌為%s,正在飛速的行駛...' %(self.color,self.brand,self.number)) 12 13 def stop(self): 14 print('車停了...') 15 16 # 2.創建對象/實例化對象 17 c1 = Car() 18 print(c1,type(c1)) # 得到<__main__.Car object at 0x000000000220D710> <class '__main__.Car'> 19 20 # 3.對象調用屬性 21 print(c1.color,c1.brand,c1.number) # 紅色 BMW 滬A88888 22 23 # 4.對象調用函數 24 c1.run() # 紅色的BMW,車牌為滬A88888,正在飛速的行駛... 25 c1.stop() # 車停了... 26 27 # 創建第二個對象 28 c2 = Car() 29 print(c2,type(c2)) # 得到<__main__.Car object at 0x000000000272D7B8> <class '__main__.Car'> 30 31 print(c1 == c2) # 得到False ,比較的是地址,c1和c2的地址不一樣 32 33 c2.color = "白色" 34 c2.brand = "BYD" 35 c2.number = "京A66666" 36 print(c2.color,c2.brand,c2.number) # 白色 BYD 京A66666 37 print(c1.color,c1.brand,c1.number) # 紅色 BMW 滬A88888 38 '''在一個模塊中可以創建多個對象,它們彼此之間是相互獨立存在(堆空間有體現),切互不幹擾...''' 39 40 c3 = c1 # c1和c3的地址是一樣的,共用,c1不調用了,但c3還指在堆上,c1記錄的地址又給到c3一份 41 c1 = None 42 '''此時堆中有1個空間,不存在垃圾空間;因為c1雖然被賦值為None了,但是c3仍然記錄了堆中對象空間的地址(維護這層關係)'''
類的成員:
1). 欄位:普通欄位、靜態欄位
普通欄位需要通過對象來訪問,而靜態欄位通過類訪問,在使用上可以看出普通欄位和靜態欄位的歸屬是不同的;他們的本質的區別是記憶體中保存的位置不同,普通欄位屬於對象,而靜態欄位屬於類;靜態欄位在記憶體中只保存一份,普通欄位在每個對象中都要保存一份;通過類創建對象時,如果每個對象都具有相同的欄位,那麼就使用靜態欄位。
1 class Province: 2 3 # 靜態欄位/類屬性 4 country = '中國' 5 6 def __init__(self, name): 7 # 普通欄位/對象屬性 8 self.name = name 9 10 # 實例對象obj1 11 obj1 = Province('河北省') 12 print(obj.name) # 直接訪問普通欄位 13 14 # 實例對象obj2 15 obj2 = Province('河南省') 16 print(obj.name) # 直接訪問普通欄位 17 18 # 直接訪問靜態欄位 19 Province.country
2). 方法:普通方法、類方法、靜態方法、屬性方法
前三種方法在記憶體中都歸屬於類,區別在於調用方式不同。普通方法:由對象調用;至少一個self參數;執行普通方法時,自動將調用該方法的對象賦值給self;類方法:由類調用;至少一個cls參數;執行類方法時,自動將調用該方法的類複製給cls;靜態方法:由類調用;無預設參數。
相同點:對於所有的方法而言,均屬於類(非對象)中,所以,在記憶體中也只保存一份;不同點:方法調用者不同、調用方法時自動傳入的參數不同。
靜態方法意思是把 @staticmethod 下麵的函數和所屬的類截斷了,這個函數就不屬於這個類了,沒有類的屬性了,只不是還是要通過類名的方式調用 ,把靜態方法當作一個獨立的函數給他傳參就行了。
類方法只能訪問類變數,不能訪問實例變數。
屬性方法把一個方法變成一個靜態屬性,屬性就不用加小括弧那樣的去調用了
1 class Foo: 2 3 def __init__(self, name): 4 self.name = name 5 6 def ord_func(self): 7 """ 定義普通方法,至少有一個self參數 """ 8 print('普通方法') 9 10 @classmethod 11 def class_func(cls): 12 """ 定義類方法,至少有一個cls參數 """ 13 print('類方法') 14 15 @staticmethod 16 def static_func(): 17 """ 定義靜態方法 ,無預設參數""" 18 print('靜態方法') 19 20 # 調用普通方法 21 f = Foo() 22 f.ord_func() 23 24 # 調用類方法 25 Foo.class_func() 26 27 # 調用靜態方法 28 Foo.static_func()
屬性方法:其實是方法裡面普通方法的變種。屬性方法的定義和調用要註意:定義時,在普通方法的基礎上添加 @property 裝飾器;定義時,屬性僅有一個self參數調用時,無需括弧。補充:屬性存在意義是:訪問屬性時可以製造出和訪問欄位完全相同的假象,屬性由方法變種而來,如果Python中沒有屬性,方法完全可以代替其功能。Python的屬性的功能是:屬性內部進行一系列的邏輯計算,最終將計算結果返回。
1 class Foo: 2 3 # 定義普通方法 4 def func(self): 5 pass 6 7 # 定義屬性 8 @property 9 def prop(self): 10 pass 11 12 # 實例化對象 13 foo_obj = Foo() 14 15 foo_obj.func() # 調用普通方法 16 foo_obj.prop # 調用屬性方法
屬性方法的兩種定義方式:裝飾器 即:在方法上應用裝飾器;靜態欄位 即:在類中定義值為property對象的靜態欄位。
1 # 裝飾器方式:在類的普通方法上應用@property裝飾器 2 class Goods: 3 4 @property 5 def price(self): 6 return "wupeiqi" 7 8 obj = Goods() 9 10 obj.price # 自動執行 @property 修飾的 price 方法,並獲取方法的返回值 11 12 13 # 靜態欄位方式,創建值為property對象的靜態欄位 14 class Foo: 15 16 def get_bar(self): 17 return 'wupeiqi' 18 19 BAR = property(get_bar) 20 21 obj = Foo() 22 23 obj.BAR # 自動調用get_bar方法,並獲取方法的返回值
類特殊成員:
魔術函數:__開頭並且__結尾的函數,我們稱為魔術函數;特點:調用執行都不需要程式員關註,系統自行決定;例如:__init__、__del__、__str__ 、...... 構造函數,析構函數, 重寫函數,...... 。
構造函數(constructor):又稱構造方法/構造器,在生成對象時調用,一個對象只會被執行一次,可以用來進行一些初始化操作,不需要顯示去調用,系統會預設去執行。
格式:__init__(self):
執行時機:在創建對象時被執行
1 class Person(object):
2
3 def __init__(self,name,age):
4 self.name = name
5 self.age = age
6 self.address = '中國'
6
7 def details(self):
8 print('姓名為:%s,年齡為:%d,籍貫是:%s' %(self.name,self.age,self.address))
9
10 # 創建對象
11 p1 = Person("多多",18) # 構造方法,通過類創建對象時,自動觸發執行
12 # 創建第二個對象,與p1互不幹擾,共同存在與堆中
13 p2 = Person("老王",28)
14 p2.details()
析構函數:當對象在記憶體中被釋放時,自動觸發執行,此方法一般無須定義,因為Python是一門高級語言,程式員在使用時無需關心記憶體的分配和釋放,因為此工作都是交給Python解釋器來執行,所以,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。這個方法預設是不需要寫的,不寫的時候,預設是不做任何操作的。因為不知道對象是在什麼時候被垃圾回收掉,所以,除非確實要在這裡面做某些操作,不然不要自定義這個方法。
格式:__del__(self):
執行時機:在程式結束前,將對象回收,清出記憶體
1 class Dog: 2 3 def __init__(self,name,age,color): 4 print('我是構造函數...') 5 self.name = name 6 self.age = age 7 self.color = color 8 9 def __del__(self): 10 print('我是析構函數...') 11 12 def func(self): 13 print('我是func函數...')
__str__(self)函數:如果一個類中定義了__str__方法,那麼在列印 對象 時,預設輸出該方法的返回值
對象實例化之後將數據給到對象名,此時如果列印對象名,在控制臺上我們看到的是整個對象的類型以及在記憶體中的地址(十六進位),但是我們在開發過程中,對於類型和地址並不關註;我們更希望看到的是對象的各個屬性內容,此時我們可以自己重新定義__str__(self)函數的函數體(就是函數重寫),此函數有返回值,return後面的內容必須是str類型
執行時機:在列印對象名/引用名時被觸發
1 # 沒有定義__str__方法時,列印創建的對象 2 class Person(object): 3 4 def __init__(self,name,age,address): 5 self.name = name 6 self.age = age 7 self.address = address 8 9 p = Person('韓梅梅',20,'上海') 10 print(p) # <__main__.Person object at 0x0000000004C3D978> 11 12 13 # 有定義__str__方法時,列印創建的對象 14 class Person(object): 15 16 def __init__(self,name,age,address): 17 self.name = name 18 self.age = age 19 self.address = address 20 21 def __str__(self): 22 return '姓名為:%s,年齡為:%d,籍貫是:%s人' %(self.name,self.age,self.address) # return後面必須是字元串數據 23 24 p = Person('韓梅梅',20,'上海') 25 print(p) # 姓名為:韓梅梅,年齡為:20,籍貫是:上海人
__dict__方法:這個方法是以字典的形式列出類或對象中的所有成員,在類裡面有,在對象裡面也有。
1 class abc: 2 def __init__(self,age): 3 self.age=age 4 def __add__(self,obj): 5 return self.age+obj.age 6 7 a1=abc(18) 8 9 print(abc.__dict__) # 類裡面的所有成員{'__add__': <function abc.__add__ at 0x0000020666C9E2F0>, '__module__': '__main__', '__weakref__': 10 # <attribute '__weakref__' of 'abc' objects>, '__init__': <function abc.__init__ at 0x0000020666C9E268>, '__doc__': None, 11 # '__dict__': <attribute '__dict__' of 'abc' objects>} 12 13 print(a1.__dict__) # 對象里的成員{'age': 18}
面向對象的三大特性:封裝性、繼承性、多態性
1). 封裝(Encapsulation):將內容封裝到某個地方,以後再去調用被封裝在某處的內容。 對於面向對象的封裝來說,其實就是使用構造方法將內容封裝到 對象 中,然後通過對象直接或者self間接獲取被封裝的內容。
電腦層面:①.模塊、類、函數...②.屬性數據的封裝與隱藏 (數據私有化);封裝的好處:安全性提高了。
場景演示:
1 class Person: 2 def __init__(self,name,age,money): 3 self.name = name 4 self.age = age 5 self.money = money 6 def __str__(self): 7 return "name:%s,age:%s,money:%s"%(self.name,self.age,self.money) 8 9 # 實例化Person對象 10 p = Person('tom',30,10000) 11 print(p) # name:tom,age:30,money:10000 12 13 # age可以設置值,但是不符邏輯了 14 p.age=-40 15 print(p) # name:tom,age:-40,money:10000
以上情況不會出現編譯和運行異常,但是出現了數據不符合邏輯的情況;關係到對象直接在外部去操作數據(屬性),導致"臟數據"的出現;要解決此問題,需要將上面的年齡age私有化,一旦私有化age之後,那麼age使用的方位只有在class中,出了class外界無法使用他,使用__屬性名的方式。
1).首先第一步是在外界不允許對象直接操作/訪問屬性(將此權利沒收) --> 將屬性私有化:__屬性名
2).需要在類的內部提供給外界額外的訪問方式(函數:getter/setter)
1 class Person: 2 def __init__(self,name,age,money): 3 self.name = name 4 self.__age = age 5 self.money = money 6 def __str__(self): 7 return "name:%s,age:%s,money:%s"%(self.name,self.__age,self.money) 8 9 # 實例化Person對象 10 p = Person('tom',30,10000) 11 print(p) # name:tom,age:30,money:10000 12 13 p.age=-40 # 現在此行代碼相當於動態為對象p添加一個屬性age 14 print(p) # name:tom,age:30,money:10000,年齡任然是30 15 16 # 查看對象p中所有的成員變數(屬性) 17 print(p.__dict__) # 得到{'name': 'tom', '_Person__age': 30, 'money': 10000}
18 '''所以age私有化之後,在電腦底層真正的名字已經變成了_Person__age,一個屬性一旦被私有化,在底層真正的名字是:_類名__屬性名 19 其實python的私有化我們可以理解為偽私有(只是換了個名)''' 20 21 # 以下的操作僅僅是為對象p動態添加一個屬性為__age 22 p.__age=-50 23 print(p) # 仍然得到name:tom,age:30,money:10000 24 print(p.__dict__) # 再看屬性{'name': 'tom', '_Person__age': 30, 'money': 10000, '__age': -50} ,只是多了一個名為__age的參數 25 26 '''但是動態數據還是可以改的(但是不要去改,這樣私有化就沒意義了)''' 27 p._Person__age = -100 28 print(p) # 得到name:tom,age:-100,money:10000
私有化之後可以不會出現邏輯不符的現象,但是對於age,需要在類的內部提供給外界額外的訪問方式(函數:getter/setter);
格式:get屬性名(self)-->有返回值;set屬性名(self,變數參數)-->有返回值;
以上兩個函數的屬性名都滿足首字母大寫其餘字母小寫的規範。
1 class Person: 2 def __init__(self,name,age,money): 3 self.name = name 4 self.__age = age 5 self.money = money 6 7 # 設置__age 8 def setAge(self,age): 9 # 對age值進行合法性的校驗 10 if age < 0 or age > 130: 11 raise Exception('年齡不合法...') 12 else: 13 self.__age = age 14 15 # 獲取__age 16 def getAge(self): 17 return self.__age
18 def __str__(self): 19 return "name:%s,age:%s,money:%s"%(self.name,self.__age,self.money) 20 21 p = Person('tom',30,10000) 22 print(p) # name:tom,age:30,money:10000 23 24 # 調用函數完成設置和獲取屬性值的操作 25 print(p.getAge()) # 30 26 p.setAge(40) 27 print(p) # name:tom,age:40,money:10000 28 29 p.setAge(-40) 30 print(p) # Exception: 年齡不合法...
總結:python的類中只有私有成員和公有成員兩種,不像c++中的類有公有成員(public),私有成員(private)和保護成員(protected).並且python中沒有關鍵字去修飾成員,預設python中所有的成員都是公有成員,但是私有成員是以兩個下劃線開頭的名字標示私有成員,私有成員不允許直接訪問,只能通過內部方法去訪問,私有成員也不允許被繼承。在類的內部提供外界額外的訪問方式(定義setter和getter方法),並且在需要的時候,可以在函數的內部加入數據合法性的校驗;模板:對於setter函數,命名:set屬性名(首字母大寫);對於getter函數,命名:get屬性名(首字母大寫)
2). 繼承性(Inheritance):面向對象中的繼承和現實生活中的繼承相同,即:子可以繼承父的內容。電腦層面:兩部分組成,一部分我們稱為父類(基類、超類、superclass);另一部分我們稱為子類(派生類、subclass);子類可以使用父類中的成員(使用權)。
繼承性的好處:1).代碼復用性變強;2).代碼擴展性變強;3).代碼維護性變好;4).代碼閱讀性變好;繼承性弊端:類和類之間是一種強耦合關係,繼承的好處要遠遠多於弊端,所以我們還是要經常使用繼承的(合理),但不能為了繼承而繼承。
繼承體系可以很龐大(呈現樹狀結構圖),越往上層的類,感覺越模糊,越不清晰越往下層的類,感覺越清晰,越具體,所以得出結論,開發過程中創建父類的可能性變低,子類實例化的可能性極高。註意事項:1).由於繼承的特點,子類對象被實例化,但是可能需要為父類屬性賦值,那麼可以在子類的構造函數中顯示的調用父類構造來實現;2) .記住:雖然父類構造被執行,但是它僅僅做的就是賦值這件事,記憶體中的對象只有子類對象一個。
分類:1).單繼承(單一繼承);2).多重繼承;3).多繼承(很多語言是不合法的)
1). 單繼承的使用:
1 # 父類 2 class Person: 3 def __init__(self,name,age): 4 print('我是Person類的構造函數。。。') 5 self.name = name 6 self.age = age 7 # 吃 8 def eat(self): 9 print('吃一個...') 10 # 睡 11 def sleep(self): 12 print('睡一會...') 13 14 # 子類 15 class Teacher(Person): 16 def __init__(self,name,age,salary): 17 print('我是teacher類的構造函數。。。') 18 self.salary = salary 19 # 在子類構造函數中顯示的調用其父類構造;調用父類構造函數的目的:父類的屬性由父類自己賦值 20 # 方法1:super(Teacher,self).__init__(name,age) 21 # 方法2:super().__init__(name,age) 22 # 方法3:調用父類,這種方式最好 23 Person.__init__(self,name,age) 24 25 # 教學 26 def teach(self): 27 print('教書育人...') 28 29 # 實例化子類對象 30 t = Teacher('老郭',30,6000.0) 31 # 調用屬性 32 print(t.name,t.age,t.salary) 33 # 調用函數 34 t.eat() 35 t.sleep() 36 t.teach()
2). 多重繼承的使用:
1 # 定義生物類: 2 class Creature: 3 def __init__(self,age): 4 self.age = age 5 6 def breath(self): 7 print('呼吸...') 8 9 # 定義動物類: 10 class Animal(Creature): 11 def __init__(self,age,name): 12 self.name = name 13 super().__init__(age) 14 15 def eat(self): 16 print('吃飯...') 17 18 # 定義狗類 19 class Dog(Animal): 20 def __init__(self,age,name,color): 21 self.color = color 22 Animal.__init__(self,age,name) 23 24 def wangwang(self): 25 print('犬吠...') 26 27 def __str__(self): 28 return "name:%s,age:%s,color:%s" %(self.name,self.age,self.color) 29 30 d = Dog(3,'旺財','black') 31 print(d) # 因為定義了__str__,即重寫,所以可以直接看到屬性,不然 print(d)得到的是列印d的地址而已,只有print(t.name,t.age,t.color),才能看到結果 33 d.wangwang() 34 d.eat() 35 d.breath()
3). 多繼承的使用:大白話就是一個子類可以調用多個父類
1 # 定義Father類 2 class Father: 3 def __init__(self,money): 4 self.money = money 5 6 def drinking(self): 7 print('喝喝喝...') 8 9 # 定義Mother類 10 class Mother: 11 def __init__(self,faceValue): 12 self.faceValue = faceValue 13 def shopping(self): 14 print('買買買...') 15 16 # 定義Child類,同時繼承Father和Mother類 17 class Child(Father,Mother): 18 def __init__(self,money,faceValue,work): 19 self.work = work 20 Father.__init__(self,money) 21 Mother.__init__(self,faceValue) 22 23 def playing(self): 24 print('玩玩玩...') 25 26 # 實例化子類對象 27 child = Child(1000000,True,"語數外") 28 29 # 調用函數 30 child.playing() # 玩玩玩... 31 child.drinking() # 喝喝喝... 32 child.shopping() # 買買買...
函數重寫(覆寫,覆蓋,override) :
前提:必須有繼承性;原因:父類中的功能(函數),子類需要用,但是父類中函數的函數體內容和我現在要執行的邏輯還不相符,那麼可以將函數名保留(功能還是此功能),但是將函數體重構;註意:子類重寫父類的函數,除了函數體以外的部分,直接複製父類的即可
1 class Fu: 2 def func(self): 3 print('辟邪劍法...') 4 5 class<