23 Python 面向对象
面向過程 VS 面向對象?
面向過程的程序設計的核心是過程(流水線式思維),過程即解決問題的步驟,面向過程的設計就好比精心設計好一條流水線,考慮周全什么時候處理什么東西。
優點是:極大的降低了寫程序的復雜度,只需要順著要執行的步驟,堆疊代碼即可。
缺點是:一套流水線或者流程就是用來解決一個問題,代碼牽一發而動全身。
應用場景:一旦完成基本很少改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
?
面向對象的程序設計的核心是對象(上帝式思維),要理解對象為何物,必須把自己當成上帝,上帝眼里世間存在的萬物皆為對象,不存在的也可以創造出來。面向對象的程序設計好比如來設計西游記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題需要四個人:唐僧,沙和尚,豬八戒,孫悟空,每個人都有各自的特征和技能(這就是對象的概念,特征和技能分別對應對象的屬性和方法),然而這并不好玩,于是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經路上被搞死,又安排了一群神仙保駕護航,這些都是對象。然后取經開始,師徒四人與妖魔鬼怪神仙互相纏斗著直到最后取得真經。如來根本不會管師徒四人按照什么流程去取。
面向對象的程序設計的
優點是:解決了程序的擴展性。對某一個對象單獨修改,會立刻反映到整個體系中,如對游戲中一個人物參數的特征和技能修改都很容易。
缺點:可控性差,無法向面向過程的程序設計流水線式的可以很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題,即便是上帝也無法預測最終結果。于是我們經常看到一個游戲人某一參數的修改極有可能導致陰霸的技能出現,一刀砍死3個人,這個游戲就失去平衡。
應用場景:需求經常變化的軟件,一般需求的變化都集中在用戶層,互聯網應用,企業內部軟件,游戲等都是面向對象的程序設計大顯身手的好地方。
在python 中面向對象的程序設計并不是全部。
面向對象編程可以使程序的維護和擴展變得更簡單,并且可以大大提高程序開發效率 ,另外,基于面向對象的程序可以使它人更加容易理解你的代碼邏輯,從而使團隊開發變得更從容。
了解一些名詞:類、對象、實例、實例化
類:具有相同特征的一類事物(人、狗、老虎)
對象/實例:具體的某一個事物(隔壁阿花、樓下旺財)
初識類和對象
python中一切皆為對象,類型的本質就是類,所以,不管你信不信,你已經使用了很長時間的類了
在python中,用變量表示特征,用函數表示技能,因而具有相同特征和技能的一類事物就是‘類’,對象是則是這一類事物中具體的一個。
類的相關知識
聲明類
1 class Person: #定義一個人類 2 role = 'person' #人的角色屬性都是人 3 def walk(self): #人都可以走路,也就是有一個走路方法,也叫動態屬性 4 print("person is walking...") 類的聲明類有兩種作用:屬性引用和實例化
?屬性引用(類名.屬性)
class Person: #定義一個人類role = 'person' #人的角色屬性都是人 def walk(self): #人都可以走路,也就是有一個走路方法 print("person is walking...") print(Person.role) #查看人的role屬性 print(Person.walk) #引用人的走路方法,注意,這里不是在調用實例化:類名加括號就是實例化,會自動觸發__init__函數的運行,可以用它來為每個實例定制自己的特征
class Person: #定義一個人類role = 'person' #人的角色屬性都是人 def __init__(self,name): self.name = name # 每一個角色都有自己的昵稱; def walk(self): #人都可以走路,也就是有一個走路方法 print("person is walking...") print(Person.role) #查看人的role屬性 print(Person.walk) #引用人的走路方法,注意,這里不是在調用實例化的過程就是類——>對象的過程
原本我們只有一個Person類,在這個過程中,產生了一個egg對象,有自己具體的名字、攻擊力和生命值。
語法:對象名 = 類名(參數)
egg = Person('egon') #類名()就等于在執行Person.__init__() #執行完__init__()就會返回一個對象。這個對象類似一個字典,存著屬于這個人本身的一些屬性和方法。 #你可以偷偷的理解:egg = {'name':'egon','walk':walk}查看屬性&調用方法
print(egg.name) #查看屬性直接 對象名.屬性名 print(egg.walk()) #調用方法,對象名.方法名()關于self
self:在實例化時自動將對象/實例本身傳給__init__的第一個參數,你也可以給他起個別的名字,但是正常人都不會這么做。
因為你瞎改別人就不認識
類屬性的補充
一:我們定義的類的屬性到底存到哪里了?有兩種方式查看 dir(類名):查出的是一個名字列表 類名.__dict__:查出的是一個字典,key為屬性名,value為屬性值二:特殊的類屬性 類名.__name__# 類的名字(字符串) 類名.__doc__# 類的文檔字符串 類名.__base__# 類的第一個父類(在講繼承時會講) 類名.__bases__# 類所有父類構成的元組(在講繼承時會講) 類名.__dict__# 類的字典屬性 類名.__module__# 類定義所在的模塊 類名.__class__# 實例對應的類(僅新式類中)對象的相關知識
對象是關于類而實際存在的一個例子,即實例
對象/實例只有一種作用:屬性引用
當然了,你也可以引用一個方法,因為方法也是一個屬性,只不過是一個類似函數的屬性,我們也管它叫動態屬性。引用動態屬性并不是執行這個方法,要想調用方法和調用函數是一樣的,都需要在后面加上括號 1 def Person(*args,**kwargs): 2 self = {} 3 def attack(self,dog): 4 dog['life_value'] -= self['aggressivity'] 5 6 def __init__(name,aggressivity,life_value): 7 self['name'] = name 8 self['aggressivity'] = aggressivity 9 self['life_value'] = life_value 10 self['attack'] = attack 11 12 __init__(*args,**kwargs) 13 return self 14 15 egg = Person('egon',78,10) 16 print(egg['name']) 了解對象 1 class 類名: 2 def __init__(self,參數1,參數2): 3 self.對象的屬性1 = 參數1 4 self.對象的屬性2 = 參數2 5 6 def 方法名(self):pass 7 8 def 方法名2(self):pass 9 10 對象名 = 類名(1,2) #對象就是實例,代表一個具體的東西 11 #類名() : 類名+括號就是實例化一個類,相當于調用了__init__方法 12 #括號里傳參數,參數不需要傳self,其他與init中的形參一一對應 13 #結果返回一個對象 14 對象名.對象的屬性1 #查看對象的屬性,直接用 對象名.屬性名 即可 15 對象名.方法名() #調用類中的方法,直接用 對象名.方法名() 即可 對象小結 1 class Person: # 定義一個人類 2 role = 'person' # 人的角色屬性都是人 3 4 def __init__(self, name, aggressivity, life_value): 5 self.name = name # 每一個角色都有自己的昵稱; 6 self.aggressivity = aggressivity # 每一個角色都有自己的攻擊力; 7 self.life_value = life_value # 每一個角色都有自己的生命值; 8 9 def attack(self,dog): 10 # 人可以攻擊狗,這里的狗也是一個對象。 11 # 人攻擊狗,那么狗的生命值就會根據人的攻擊力而下降 12 dog.life_value -= self.aggressivity 13 14 class Dog: # 定義一個狗類 15 role = 'dog' # 狗的角色屬性都是狗 16 17 def __init__(self, name, breed, aggressivity, life_value): 18 self.name = name # 每一只狗都有自己的昵稱; 19 self.breed = breed # 每一只狗都有自己的品種; 20 self.aggressivity = aggressivity # 每一只狗都有自己的攻擊力; 21 self.life_value = life_value # 每一只狗都有自己的生命值; 22 23 def bite(self,people): 24 # 狗可以咬人,這里的狗也是一個對象。 25 # 狗咬人,那么人的生命值就會根據狗的攻擊力而下降 26 people.life_value -= self.aggressivity 27 28 egg = Person('egon',10,1000) #創造了一個實實在在的人egg 29 ha2 = Dog('二愣子','哈士奇',10,1000) #創造了一只實實在在的狗ha2 30 print(ha2.life_value) #看看ha2的生命值 31 egg.attack(ha2) #egg打了ha2一下 32 print(ha2.life_value) #ha2掉了10點血 游戲全解
1 from math import pi 2 3 class Circle: 4 ''' 5 定義了一個圓形類; 6 提供計算面積(area)和周長(perimeter)的方法 7 ''' 8 def __init__(self,radius): 9 self.radius = radius 10 11 def area(self): 12 return pi * self.radius * self.radius 13 14 def perimeter(self): 15 return 2 * pi *self.radius 16 17 18 circle = Circle(10) #實例化一個圓 19 area1 = circle.area() #計算圓面積 20 per1 = circle.perimeter() #計算圓周長 21 print(area1,per1) #打印圓面積和周長 計算圓的周長和面積
對象之間的交互
創建一個人類
1 class Person: # 定義一個人類 2 role = 'Person' # 人的角色屬性都是人 3 4 def __init__(self, name, sex, aggressivity, life_value): 5 self.name = name # 每一個人都有自己的昵稱; 6 self.sex = sex # 每一個人都有自己的性別; 7 self.aggressivity = aggressivity # 每一個人都有自己的攻擊力; 8 self.life_value = life_value # 每一個人都有自己的生命值; 9 10 def attack(self,people): 11 # 人攻擊狗,這里的人也是一個對象。 12 # 人攻擊狗,那么狗的生命值就會根據人的攻擊力而下降 13 people.life_value -= self.aggressivit Person創建一個狗類
1 class Dog: # 定義一個狗類 2 role = 'dog' # 狗的角色屬性都是狗 3 4 def __init__(self, name, breed, aggressivity, life_value): 5 self.name = name # 每一只狗都有自己的昵稱; 6 self.breed = breed # 每一只狗都有自己的品種; 7 self.aggressivity = aggressivity # 每一只狗都有自己的攻擊力; 8 self.life_value = life_value # 每一只狗都有自己的生命值; 9 10 def bite(self,people): 11 # 狗可以咬人,這里的狗也是一個對象。 12 # 狗咬人,那么人的生命值就會根據狗的攻擊力而下降 13 dog.life_value -= self.aggressivit Dog實例化一只實實在在的二哈
ha2 = Dog('二愣子','哈士奇',10,1000) #創造了一只實實在在的狗ha2?
交互 egon打ha2一下
print(ha2.life_value) #看看ha2的生命值 egg.attack(ha2) #egg打了ha2一下 print(ha2.life_value) #ha2掉了10點血完整的代碼
1 class Person: # 定義一個人類 2 role = 'person' # 人的角色屬性都是人 3 4 def __init__(self, name, aggressivity, life_value): 5 self.name = name # 每一個角色都有自己的昵稱; 6 self.aggressivity = aggressivity # 每一個角色都有自己的攻擊力; 7 self.life_value = life_value # 每一個角色都有自己的生命值; 8 9 def attack(self,dog): 10 # 人可以攻擊狗,這里的狗也是一個對象。 11 # 人攻擊狗,那么狗的生命值就會根據人的攻擊力而下降 12 dog.life_value -= self.aggressivity 13 14 class Dog: # 定義一個狗類 15 role = 'dog' # 狗的角色屬性都是狗 16 17 def __init__(self, name, breed, aggressivity, life_value): 18 self.name = name # 每一只狗都有自己的昵稱; 19 self.breed = breed # 每一只狗都有自己的品種; 20 self.aggressivity = aggressivity # 每一只狗都有自己的攻擊力; 21 self.life_value = life_value # 每一只狗都有自己的生命值; 22 23 def bite(self,people): 24 # 狗可以咬人,這里的狗也是一個對象。 25 # 狗咬人,那么人的生命值就會根據狗的攻擊力而下降 26 people.life_value -= self.aggressivity 27 28 egg = Person('egon',10,1000) #創造了一個實實在在的人egg 29 ha2 = Dog('二愣子','哈士奇',10,1000) #創造了一只實實在在的狗ha2 30 print(ha2.life_value) #看看ha2的生命值 31 egg.attack(ha2) #egg打了ha2一下 32 print(ha2.life_value) #ha2掉了10點血 egon大戰哈士奇 1 from math import pi 2 3 class Circle: 4 ''' 5 定義了一個圓形類; 6 提供計算面積(area)和周長(perimeter)的方法 7 ''' 8 def __init__(self,radius): 9 self.radius = radius 10 11 def area(self): 12 return pi * self.radius * self.radius 13 14 def perimeter(self): 15 return 2 * pi *self.radius 16 17 18 circle = Circle(10) #實例化一個圓 19 area1 = circle.area() #計算圓面積 20 per1 = circle.perimeter() #計算圓周長 21 print(area1,per1) #打印圓面積和周長 簡單的類類命名空間與對象、實例的命名空間
創建一個類就會創建一個類的名稱空間,用來存儲類中定義的所有名字,這些名字稱為類的屬性
而類有兩種屬性:靜態屬性和動態屬性
- 靜態屬性就是直接在類中定義的變量
- 動態屬性就是定義在類中的方法
其中類的數據屬性是共享給所有對象的
>>>id(egg.role) 4341594072 >>>id(Person.role) 4341594072?
而類的動態屬性是綁定到所有對象的
>>>egg.attack <bound method Person.attack of <__main__.Person object at 0x101285860>> >>>Person.attack <function Person.attack at 0x10127abf8>?創建一個對象/實例就會創建一個對象/實例的名稱空間,存放對象/實例的名字,稱為對象/實例的屬性
在obj.name會先從obj自己的名稱空間里找name,找不到則去類中找,類也找不到就找父類...最后都找不到就拋出異常
面向對象的組合用法
軟件重用的重要方式除了繼承之外還有另外一種方式,即:組合
組合指的是,在一個類中以另外一個類的對象作為數據屬性,稱為類的組合
1 class Person: # 定義一個人類 2 role = 'person' # 人的角色屬性都是人 3 4 def __init__(self, name, aggr): 5 self.name = name # 每一個角色都有自己的昵稱; 6 self.aggr = aggr 7 self.weanpon = Weapon() # 給人增加一個技能 8 9 10 egg = Person('egon', 5) 11 egg.weanpon.prick(egg) 12 print(egg.aggr) 對象組合圓環是由兩個圓組成的,圓環的面積是外面圓的面積減去內部圓的面積。圓環的周長是內部圓的周長加上外部圓的周長。
這個時候,我們就首先實現一個圓形類,計算一個圓的周長和面積。然后在"環形類"中組合圓形的實例作為自己的屬性來用
用組合的方式建立了類與組合的類之間的關系,它是一種‘有’的關系,比如教授有生日,教授教python課程
1 class BirthData: 2 def __init__(self, year, month, day): 3 self.year = year 4 self.month = month 5 self.day = day 6 7 class Couse: 8 def __init__(self, name, price, period): 9 self.name = name 10 self.price = price 11 self.period = period 12 13 14 class Teacher: 15 def __init__(self, name, gender, birth, course): 16 self.name = name 17 self.gender = gender 18 self.birth = birth 19 self.course = course 20 21 def teach(self): 22 print('teaching') 23 24 25 p1 = Teacher('egon', 'male', BirthData('l995', '1', '27'), Couse('python', '2800', '4 month')) 26 print(p1.birth.year, p1.birth.month, p1.birth.day) 27 print(p1.course.name, p1.course.price, p1.course.period) View Code當類之間有顯著不同,并且較小的類是較大的類所需要的組件時,用組合比較好
初識面向對象小結
1 # 定義一個人類 2 class Person: 3 role = 'person' 4 5 def __init__(self, name, aggressivity, life_value, money): 6 self.name = name 7 self.aggressivity = aggressivity 8 self.life_value = life_value 9 self.money = money 10 11 def attack(self, obj): 12 obj.life_value -= self.aggressivity 13 14 15 # 定義一個狗類 16 class Dog: 17 role = 'dog' 18 19 def __init__(self, name, breed, aggressivity, life_value): 20 self.name = name 21 self.breed = breed 22 self.aggressivity = aggressivity 23 self.life_value = life_value 24 25 def bite(self, obj): 26 obj.life_value -= self.aggressivity 27 28 29 # 定義一個武器和技能類 30 class Weapon: 31 def __init__(self, name, price, agrev, life_value): 32 self.name = name 33 self.price = price 34 self.agrev = agrev 35 self.life_value = life_value 36 37 def update(self, obj): # obj就是要帶這個裝備的人 38 if obj.money > self.price: 39 obj.money -= self.price # 用這個武器的人花錢買所以對應的錢要減少 40 obj.aggressivity += self.agrev # 帶上這個裝備可以讓人增加攻擊 41 obj.life_value += self.life_value # 帶上這個裝備可以讓人增加生命值 42 43 def prick(self, obj): # 這是該裝備的主動技能,扎死對方 44 obj.life_value -= 500 # 攻擊力是500 45 46 47 # 測試交互 48 lance = Weapon('長矛', 200, 6, 100) 49 egg = Person('egon', 10, 1000, 600) # 創造了一個實實在在的人egg 50 ha2 = Dog('二哈', '哈士奇', 10, 1000) # 創造了一只實實在在的狗ha2 51 lance.update(egg) # egg花錢買了一個長矛防身,且自身屬性得到了提高 52 egg.weapon = lance # egg裝備上了武器 53 print(egg.__dict__) 54 55 print(ha2.life_value) 56 egg.attack(ha2) # egg打了ha2一下 57 print(ha2.life_value) 58 egg.weapon.prick(ha2) # 發動武器技能 59 print(ha2.life_value) 人狗大戰交互?
?
面向對象的三大特性
繼承
什么是繼承
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類
python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類passclass ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類 pass查看繼承
>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看所有繼承的父類 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)提示:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)繼承與抽象(先抽象再繼承)
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:?
1.將奧巴馬和梅西這倆對象比較像的部分抽取成類;?
2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關注點,降低復雜度)
?
繼承:是基于抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
派生
當然子類也可以添加自己新的屬性或者在自己這里重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那么調用新增的屬性時,就以自己為準了
1 class Animal: 2 """ 3 人和狗都是動物,所以創造一個Animal基類 4 """ 5 def __init__(self, name, aggressivity, life_value): 6 self.name = name 7 self.aggressivity = aggressivity 8 self.life_value = life_value 9 10 def eat(self): 11 print('%s is eating' % self.name) 12 13 14 class Dog(Animal): 15 """ 16 狗類,繼承了Animal類 17 """ 18 def bite(self, people): 19 """ 20 派生,狗咬人的技能 21 :param people: 22 :return: 23 """ 24 people.life_value -= self.aggressivity 25 26 class Person(Animal): 27 """ 28 人類,繼承Animal 29 """ 30 def attack(self, dog): 31 """ 32 派生,人有攻擊的技能 33 :param dog: 34 :return: 35 """ 36 dog.life_value -= self.aggressivity 37 38 39 egg = Person('egon', 10, 1000) 40 ha2 = Dog('二愣子', 50, 1000) 41 42 print(ha2.life_value) 43 print(egg.attack(ha2)) 44 print(ha2.life_value) View Code注意:像ha2.life_value之類的屬性引用,會先從實例中找life_value然后去類中找,然后再去父類中找...直到最頂級的父類。
?
在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能需要重用父類中重名的那個函數功能,應該是用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,因此即便是self參數也要為其傳值.
在python3中,子類執行父類的方法也可以直接用super方法.
1 class Animal: 2 """ 3 人和狗都是動物,所以創造一個Animal基類 4 """ 5 def __init__(self, name, aggressivity, life_value): 6 self.name = name 7 self.aggressivity = aggressivity 8 self.life_value = life_value 9 10 def eat(self): 11 print('%s is eating' % self.life_value) 12 13 14 class Dog(Animal): 15 """ 16 狗類, 繼承Animal類 17 """ 18 def __init__(self, name, aggressivity, life_value, breed): 19 super().__init__(name, aggressivity, life_value) 20 self.breed = breed 21 22 def breed(self, people): 23 """ 24 派生出新的技能, 狗咬人的技能 25 :param people: 26 :return: 27 """ 28 people.life_value -= self.aggressivity 29 30 def eat(self): 31 # super().eat() 32 # Animal.eat(self) 33 print('from dog') 34 35 36 class Person(Animal): 37 """ 38 人類,繼承Animal類 39 """ 40 def __init__(self, name, aggressivity, life_value, money): 41 Animal.__init__(self, name, aggressivity, life_value) 42 # super.__init__(name, aggressivity, life_value) 43 self.money = money 44 45 def attack(self, dog): 46 """ 47 派生出了新技能, 人有攻擊的技能 48 :param dog: 49 :return: 50 """ 51 dog.life_value -= self.aggressivity 52 53 54 egg = Person('egon', 10, 1000, 600) 55 ha2 = Dog('二愣子', '哈士奇', 10, 1000) 56 print(egg.name) 57 print(ha2.name) 58 egg.eat() 59 ha2.eat() View Code通過繼承建立了派生類與基類之間的關系,它是一種'是'的關系,比如白馬是馬,人是動物。
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師
1 class Teacher: 2 def __init__(self, name, gender): 3 self.name = name 4 self.gender = gender 5 6 def teach(self): 7 print('teaching') 8 9 class Professor(Teacher): 10 pass 11 12 p1 = Professor('egon', 'male') 13 p1.teach() View Code鉆石繼承
繼承順序
1 class A(object): 2 def test(self): 3 print('from A') 4 5 class B(A): 6 def test(self): 7 print('from B') 8 9 class C(A): 10 def test(self): 11 print('from C') 12 13 class D(B): 14 def test(self): 15 print('from D') 16 17 class E(C): 18 def test(self): 19 print('from E') 20 21 class F(D,E): 22 # def test(self): 23 # print('from F') 24 pass 25 f1=F() 26 f1.test() 27 print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性 28 29 #新式類繼承順序:F->D->B->E->C->A 30 #經典類繼承順序:F->D->B->A->E->C 31 #python3中統一都是新式類 32 #pyhon2中才分新式類與經典類 繼承順序繼承原理
python到底是如何實現繼承的,對于你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如
>>> F.mro() #等同于F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]?
為了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構造是通過一個C3線性化算法來實現的。我們不去深究這個算法的數學原理,它實際上就是合并所有父類的MRO列表并遵循如下三條準則:
1.子類會先于父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類
繼承小結
繼承的作用
減少代碼的重用 提高代碼可讀性 規范編程模式幾個名詞
抽象:抽象即抽取類似或者說比較像的部分。是一個從具體到抽象的過程。 繼承:子類繼承了父類的方法和屬性 派生:子類在父類方法和屬性的基礎上產生了新的方法和屬性抽象類與接口類
1.多繼承問題 在繼承抽象類的過程中,我們應該盡量避免多繼承; 而在繼承接口的時候,我們反而鼓勵你來多繼承接口2.方法的實現 在抽象類中,我們可以對一些抽象方法做出基礎實現; 而在接口類中,任何方法都只是一種規范,具體的功能需要子類實現鉆石繼承
新式類:廣度優先 經典類:深度優先?
多態
多態
多態指的是一類事物有多種形態
動物有多種形態:人,狗,豬
1 class Animal(metaclass=abc.ABCMeta): # 同一類事物:動物 2 @abc.abstractmethod 3 def talk(self): 4 pass 5 6 7 class People(Animal): # 動物的形態之一:人 8 def talk(self): 9 print('say hello') 10 11 12 class Dog(Animal): # 動物的形態之二:狗 13 def talk(self): 14 print('say wangwang') 15 16 17 class pig(Animal): # 動物的形態之三:豬 18 def talk(self): 19 print('say aoao') 動物文件有多種形態:文本文件,可執行文件
1 import abc 2 class File(metaclass=abc.ABCMeta): # 同一類事物:文件 3 @abc.abstractmethod 4 def cilck(self): 5 pass 6 7 8 class Text(File): # 文件的形態之一:文本文件 9 def click(self): 10 print('open file') 11 12 13 class ExeFile(File): # 文件的形態之二:可執行文件 14 def click(self): 15 print('execute file') 事物多態性
一 什么是多態動態綁定(在繼承的背景下使用時,有時也稱為多態性)
多態性是指在不考慮實例類型的情況下使用實例
在面向對象方法中一般是這樣表述多態性: 向不同的對象發送同一條消息(!!!obj.func():是調用了obj的方法func,又稱為向obj發送了一條消息func),不同的對象在接收時會產生不同的行為(即方法)。 也就是說,每個對象可以用自己的方式去響應共同的消息。所謂消息,就是調用函數,不同的行為就是指不同的實現,即執行不同的函數。比如:老師.下課鈴響了(),學生.下課鈴響了(),老師執行的是下班操作,學生執行的是放學操作,雖然二者消息一樣,但是執行的效果不同?
多態性
peo=People() dog=Dog() pig=Pig()#peo、dog、pig都是動物,只要是動物肯定有talk方法 #于是我們可以不用考慮它們三者的具體是什么類型,而直接使用 peo.talk() dog.talk() pig.talk() #更進一步,我們可以定義一個統一的接口來使用 def func(obj): obj.talk()?
鴨子類型
逗比時刻:
Python崇尚鴨子類型,即‘如果看起來像、叫聲像而且走起路來像鴨子,那么它就是鴨子’
python程序員通常根據這種行為來編寫程序。例如,如果想編寫現有對象的自定義版本,可以繼承該對象
也可以創建一個外觀和行為像,但與它無任何關系的全新對象,后者通常用于保存程序組件的松耦合度。
例1:利用標準庫中定義的各種‘與文件類似’的對象,盡管這些對象的工作方式像文件,但他們沒有繼承內置文件對象的方法
例2:序列類型有多種形態:字符串,列表,元組,但他們直接沒有直接的繼承關系
1 # 二者都像鴨子,二者看起來都像文件,因而就可以當文件一樣去用 2 class TxtFile: 3 def read(self): 4 pass 5 6 def write(self): 7 pass 8 9 10 class DiskFile: 11 def read(self): 12 pass 13 14 def write(self): 15 pass 鴨子類型?
封裝
【封裝】
?? ????? 隱藏對象的屬性和實現細節,僅對外提供公共訪問方式。
【好處】?
1. 將變化隔離;?
2. 便于使用;
3. 提高復用性;?
4. 提高安全性;
【封裝原則】
????? 1. 將不需要對外提供的內容都隱藏起來;
????? 2. 把屬性都隱藏,提供公共方法對其訪問。
私有變量和私有方法
在python中用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的)
私有變量
這種自動變形的特點:
1.類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。
2.這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。
3.在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。
?
這種變形需要注意的問題是:
1.這種機制也并沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然后就可以訪問了,如a._A__N
2.變形的過程只在類的內部生效,在定義后的賦值操作,不會變形
私有方法
3.在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的
?
封裝與擴展性
封裝在于明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足為慮。
1 #類的設計者 2 class Room: 3 def __init__(self,name,owner,width,length,high): 4 self.name=name 5 self.owner=owner 6 self.__width=width 7 self.__length=length 8 self.__high=high 9 def tell_area(self): #對外提供的接口,隱藏了內部的實現細節,此時我們想求的是面積 10 return self.__width * self.__length 11 12 13 #使用者 14 >>> r1=Room('臥室','egon',20,20,20) 15 >>> r1.tell_area() #使用者調用接口tell_area 16 17 18 #類的設計者,輕松的擴展了功能,而類的使用者完全不需要改變自己的代碼 19 class Room: 20 def __init__(self,name,owner,width,length,high): 21 self.name=name 22 self.owner=owner 23 self.__width=width 24 self.__length=length 25 self.__high=high 26 def tell_area(self): #對外提供的接口,隱藏內部實現,此時我們想求的是體積,內部邏輯變了,只需求修該下列一行就可以很簡答的實現,而且外部調用感知不到,仍然使用該方法,但是功能已經變了 27 return self.__width * self.__length * self.__high 28 29 30 #對于仍然在使用tell_area接口的人來說,根本無需改動自己的代碼,就可以用上新功能 31 >>> r1.tell_area() 私有的屬性property屬性
什么是特性property
property是一種特殊的屬性,訪問它時會執行一段功能(函數)然后返回值
1 例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便于理解) 2 3 成人的BMI數值: 4 過輕:低于18.5 5 正常:18.5-23.9 6 過重:24-27 7 肥胖:28-32 8 非常肥胖, 高于32 9 體質指數(BMI)=體重(kg)÷身高^2(m) 10 EX:70kg÷(1.75×1.75)=22.86 11 12 13 class People: 14 def __init__(self,name,weight,height): 15 self.name=name 16 self.weight=weight 17 self.height=height 18 @property 19 def bmi(self): 20 return self.weight / (self.height**2) 測試bmi值 1 mport math 2 class Circle: 3 def __init__(self,radius): #圓的半徑radius 4 self.radius=radius 5 6 @property 7 def area(self): 8 return math.pi * self.radius**2 #計算面積 9 10 @property 11 def perimeter(self): 12 return 2*math.pi*self.radius #計算周長 13 14 c=Circle(10) 15 print(c.radius) 16 print(c.area) #可以向訪問數據屬性一樣去訪問area,會觸發一個函數的執行,動態計算出一個值 17 print(c.perimeter) #同上 18 ''' 19 輸出結果: 20 314.1592653589793 21 62.83185307179586 22 ''' 圓的周長和面積為什么要用property
將一個類的函數定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然后計算出來的,這種特性的使用方式遵循了統一訪問的原則
ps:面向對象的封裝有三種方式:【public】
這種其實就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,
但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什么大家 不說“女兒”,
就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開
【private】
這種封裝對誰都不公開
python并沒有在語法上把它們三個內建到自己的class機制中,
在C++里一般會將所有的所有的數據都設置為私有的,然后提供set和get方法(接口)去設置和獲取,
在python中通過property方法可以實現
1 class Foo: 2 def __init__(self,val): 3 self.__NAME=val #將所有的數據屬性都隱藏起來 4 5 @property 6 def name(self): 7 return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置) 8 9 @name.setter 10 def name(self,value): 11 if not isinstance(value,str): #在設定值之前進行類型檢查 12 raise TypeError('%s must be str' %value) 13 self.__NAME=value #通過類型檢查后,將值value存放到真實的位置self.__NAME 14 15 @name.deleter 16 def name(self): 17 raise TypeError('Can not delete') 私有屬性的修改和刪除
一個靜態屬性property本質就是實現了get,set,delete三種方法
1 class Classmethod_Demo(): 2 role = 'dog' 3 4 @classmethod 5 def func(cls): 6 print(cls.role) 7 8 Classmethod_Demo.func() 類方法 1 class Staticmethod_Demo(): 2 role = 'dog' 3 4 @staticmethod 5 def func(): 6 print("當普通方法用") 7 8 Staticmethod_Demo.func() 靜態方法面向對象的軟件開發
很多人在學完了python的class機制之后,遇到一個生產中的問題,還是會懵逼,這其實太正常了,因為任何程序的開發都是先設計后編程,python的class機制只不過是一種編程方式,如果你硬要拿著class去和你的問題死磕,變得更加懵逼都是分分鐘的事,在以前,軟件的開發相對簡單,從任務的分析到編寫程序,再到程序的調試,可以由一個人或一個小組去完成。但是隨著軟件規模的迅速增大,軟件任意面臨的問題十分復雜,需要考慮的因素太多,在一個軟件中所產生的錯誤和隱藏的錯誤、未知的錯誤可能達到驚人的程度,這也不是在設計階段就完全解決的。
? ? 所以軟件的開發其實一整套規范,我們所學的只是其中的一小部分,一個完整的開發過程,需要明確每個階段的任務,在保證一個階段正確的前提下再進行下一個階段的工作,稱之為軟件工程
? ? 面向對象的軟件工程包括下面幾個部:
1.面向對象分析(object oriented analysis ,OOA)
? ? 軟件工程中的系統分析階段,要求分析員和用戶結合在一起,對用戶的需求做出精確的分析和明確的表述,從大的方面解析軟件系統應該做什么,而不是怎么去做。面向對象的分析要按照面向對象的概念和方法,在對任務的分析中,從客觀存在的事物和事物之間的關系,貴南出有關的對象(對象的‘特征’和‘技能’)以及對象之間的聯系,并將具有相同屬性和行為的對象用一個類class來標識。
? ? 建立一個能反映這是工作情況的需求模型,此時的模型是粗略的。
2 面向對象設計(object oriented design,OOD)
? ? 根據面向對象分析階段形成的需求模型,對每一部分分別進行具體的設計。
? ? 首先是類的設計,類的設計可能包含多個層次(利用繼承與派生機制)。然后以這些類為基礎提出程序設計的思路和方法,包括對算法的設計。
? ? 在設計階段并不牽涉任何一門具體的計算機語言,而是用一種更通用的描述工具(如偽代碼或流程圖)來描述
3 面向對象編程(object oriented programming,OOP)
? ? 根據面向對象設計的結果,選擇一種計算機語言把它寫成程序,可以是python
4 面向對象測試(object oriented test,OOT)
? ? 在寫好程序后交給用戶使用前,必須對程序進行嚴格的測試,測試的目的是發現程序中的錯誤并修正它。
? ? 面向對的測試是用面向對象的方法進行測試,以類作為測試的基本單元。
5 面向對象維護(object oriendted soft maintenance,OOSM)
? ? 正如對任何產品都需要進行售后服務和維護一樣,軟件在使用時也會出現一些問題,或者軟件商想改進軟件的性能,這就需要修改程序。
? ? 由于使用了面向對象的方法開發程序,使用程序的維護比較容易。
? ? 因為對象的封裝性,修改一個對象對其他的對象影響很小,利用面向對象的方法維護程序,大大提高了軟件維護的效率,可擴展性高。
?
? ? 在面向對象方法中,最早發展的肯定是面向對象編程(OOP),那時OOA和OOD都還沒有發展起來,因此程序設計者為了寫出面向對象的程序,還必須深入到分析和設計領域,尤其是設計領域,那時的OOP實際上包含了現在的OOD和OOP兩個階段,這對程序設計者要求比較高,許多人感到很難掌握。
? ? 現在設計一個大的軟件,是嚴格按照面向對象軟件工程的5個階段進行的,這個5個階段的工作不是由一個人從頭到尾完成的,而是由不同的人分別完成,這樣OOP階段的任務就比較簡單了。程序編寫者只需要根據OOd提出的思路,用面向對象語言編寫出程序既可。
? ? 在一個大型軟件開發過程中,OOP只是很小的一個部分。
? ? 對于全棧開發的你來說,這五個階段都有了,對于簡單的問題,不必嚴格按照這個5個階段進行,往往由程序設計者按照面向對象的方法進行程序設計,包括類的設計和程序的設計
幾個概念的說明
?
1.面向對象的程序設計看起來高大上,所以我在編程時就應該保證通篇class,這樣寫出的程序一定是好的程序(面向對象只適合那些可擴展性要求比較高的場景)
2.很多人喜歡說面向對象三大特性(這是從哪傳出來的,封裝,多態,繼承?漏洞太多太多,好吧暫且稱為三大特性),那么我在基于面向對象編程時,我一定要讓我定義的類中完整的包含這三種特性,這樣寫肯定是好的程序
好家伙,我說降龍十八掌有十八掌,那么你每次跟人干仗都要從第一掌打到第18掌這才顯得你會了是么:面對敵人,你打到第三掌對方就已經倒下了,你說,不行,你給老子起來,老子還沒有show完...
3.類有類屬性,實例有實例屬性,所以我們在定義class時一定要定義出那么幾個類屬性,想不到怎么辦,那就使勁的想,定義的越多越牛逼
這就犯了一個嚴重的錯誤,程序越早面向對象,死的越早,為啥面向對象,因為我們要將數據與功能結合到一起,程序整體的結構都沒有出來,或者說需要考慮的問題你都沒有搞清楚個八九不離十,你就開始面向對象了,這就導致了,你在那里干想,自以為想通了,定義了一堆屬性,結果后來又都用不到,或者想不通到底應該定義啥,那就一直想吧,想著想著就瘋了。
你見過哪家公司要開發一個軟件,上來就開始寫,肯定是頻繁的開會討論計劃,請看第八節。
面向對象常用術語
抽象/實現
抽象指對現實世界問題和實體的本質表現,行為和特征建模,建立一個相關的子集,可以用于 繪程序結構,從而實現這種模型。抽象不僅包括這種模型的數據屬性,還定義了這些數據的接口。
對某種抽象的實現就是對此數據及與之相關接口的現實化(realization)。現實化這個過程對于客戶 程序應當是透明而且無關的。?
封裝/接口
封裝描述了對數據/信息進行隱藏的觀念,它對數據屬性提供接口和訪問函數。通過任何客戶端直接對數據的訪問,無視接口,與封裝性都是背道而馳的,除非程序員允許這些操作。作為實現的 一部分,客戶端根本就不需要知道在封裝之后,數據屬性是如何組織的。在Python中,所有的類屬性都是公開的,但名字可能被“混淆”了,以阻止未經授權的訪問,但僅此而已,再沒有其他預防措施了。這就需要在設計時,對數據提供相應的接口,以免客戶程序通過不規范的操作來存取封裝的數據屬性。
注意:封裝絕不是等于“把不想讓別人看到、以后可能修改的東西用private隱藏起來”
真正的封裝是,經過深入的思考,做出良好的抽象,給出“完整且最小”的接口,并使得內部細節可以對外透明
(注意:對外透明的意思是,外部調用者可以順利的得到自己想要的任何功能,完全意識不到內部細節的存在)
合成
合成擴充了對類的 述,使得多個不同的類合成為一個大的類,來解決現實問題。合成 述了 一個異常復雜的系統,比如一個類由其它類組成,更小的組件也可能是其它的類,數據屬性及行為, 所有這些合在一起,彼此是“有一個”的關系。
派生/繼承/繼承結構
派生描述了子類衍生出新的特性,新類保留已存類類型中所有需要的數據和行為,但允許修改或者其它的自定義操作,都不會修改原類的定義。
繼承描述了子類屬性從祖先類繼承這樣一種方式
繼承結構表示多“代”派生,可以述成一個“族譜”,連續的子類,與祖先類都有關系。
泛化/特化
基于繼承
泛化表示所有子類與其父類及祖先類有一樣的特點。
特化描述所有子類的自定義,也就是,什么屬性讓它與其祖先類不同。
多態與多態性
多態指的是同一種事物的多種狀態:水這種事物有多種不同的狀態:冰,水蒸氣
多態性的概念指出了對象如何通過他們共同的屬性和動作來操作及訪問,而不需考慮他們具體的類。
冰,水蒸氣,都繼承于水,它們都有一個同名的方法就是變成云,但是冰.變云(),與水蒸氣.變云()是截然不同的過程,雖然調用的方法都一樣
自省/反射
自省也稱作反射,這個性質展示了某對象是如何在運行期取得自身信息的。如果傳一個對象給你,你可以查出它有什么能力,這是一項強大的特性。如果Python不支持某種形式的自省功能,dir和type內建函數,將很難正常工作。還有那些特殊屬性,像__dict__,__name__及__doc__
?
?
轉載于:https://www.cnblogs.com/panfb/p/7852483.html
總結
以上是生活随笔為你收集整理的23 Python 面向对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GitLab搭建详细过程
- 下一篇: 关于python中excel写入案例