面向物件的編程(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 =https://www.cnblogs.com/bonheur/p/ 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 Zi(Fu): 6 def func(self): 7 super().func() 8 print('葵花寶典...') 9 10 # 實體化子類物件 11 zi = Zi() 12 13 zi.func() # 得到 辟邪劍法 14 # 葵花寶典
......
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/179638.html
標籤:Python
