楔子
你現在是一家游戲公司的開發人員,現在需要你開發一款叫做<人狗大戰>的游戲,你就思考呀,人狗作戰,那至少需要2個角色,一個是人, 一個是狗,且人和狗都有不同的技能,比如人拿棍打狗, 狗可以咬人,怎么描述這種不同的角色和他們的功能呢? 你搜羅了自己掌握的所有技能,寫出了下面的代碼來描述這兩個角色def person(name,age,sex,job):
data =https://www.cnblogs.com/Golanguage/p/ {
'name':name,
'age':age,
'sex':sex,
'job':job
}
return data
def dog(name,dog_type):
data =https://www.cnblogs.com/Golanguage/p/ {
'name':name,
'type':dog_type
}
return data
上面兩個方法相當于造了兩個模子,游戲里的每個人和每條狗都擁有相同里的屬性,游戲開始,你根據一個人或一只狗傳入的具體資訊來塑造一個具體的人或者狗,怎么生成呢?
d1 = dog("李磊","京巴")
p1 = person("嚴帥",36,"F","運維")
p2 = person("egon",27,"F","Teacher")
兩個角色物件生成了,狗和人還有不同的功能呀,狗會咬人,人會打狗,對不對? 怎么實作呢,,,想到了, 可以每個功能再寫一個函式,想執行哪個功能,直接 呼叫 就可以了,對不?
def bark(d):
print("dog %s:wang.wang..wang..."%d['name'])
def walk(p):
print("person %s is walking..." %p['name'])<br><br>
walk(p1)
bark(d1)
上面的功能實作的簡直是完美!
但是仔細玩耍一會,你就不小心干了下面這件事
p1 = person("嚴帥",36,"F","運維")
bark(p1) #把人的物件傳給了狗的方法
事實 上,從你寫的代碼上來看,這并沒出錯,很顯然,人是不能呼叫狗的功能的,但在你的程式例沒有做限制,如何在代碼級別實作這個限制呢?
def person(name,age,sex,job): def walk(p): print("person %s is walking..." % p['name']) data = { 'name':name, 'age':age, 'sex':sex, 'job':job, 'walk':walk } return data def dog(name,dog_type): def bark(d): print("dog %s:wang.wang..wang..."%d['name']) data = { 'name':name, 'type':dog_type, 'bark':bark } return data限制功能全新代碼
d1 = dog("李磊","京巴") p1 = person("嚴帥",36,"F","運維") p2 = person("egon",27,"F","Teacher")生產具體的人和汪
d1['bark'](p1) #把人傳給了狗的方法無法呼叫了
你是如此的機智,這樣就實作了限制人只能用人自己的功能啦,
剛剛你用的這種編程思想其實就是簡單的面向物件編程,我們創造了兩個模子表示游戲里所有的人和狗之后,剩下的狗叫或者人走對于這兩個模子來說就不重要了,具體人he狗之間的互動就等著你去使用了,假如你和狗打起來了,這時候你是走路還是拿棍子打狗就由你自己決定了,那你的每一個決定可能都影響著你這場游戲的輸贏,這也是不確定的,和我們之前寫代碼按部就班的走,最終都會實作我們要完成的事情不太一樣了,
盡管如此,我們也只完成了這個游戲非常小的一部分,還有很多功能都沒有實作,
剛才你只是阻止了兩個完全 不同的角色 之前的功能混用, 但有沒有可能 ,同一個種角色,但有些屬性是不同的呢? 比如 ,大家都打過cs吧,cs里有警察和恐怖份子,但因為都 是人, 所以你寫一個角色叫 person(), 警察和恐怖份子都 可以 互相射擊,但警察不可以殺人質,恐怖分子可以,這怎么實作呢? 你想了說想,說,簡單,只需要在殺人質的功能里加個判斷,如果是警察,就不讓殺不就ok了么, 沒錯, 這雖然 解決了殺人質的問題,但其實你會發現,警察和恐怖分子的區別還有很多,同時又有很多共性,如果 在每個區別處都 單獨做判斷,那得累死,
你想了想說, 那就直接寫2個角色吧, 反正 這么多區別, 我的哥, 不能寫兩個角色呀,因為他們還有很多共性 , 寫兩個不同的角色,就代表 相同的功能 也要重寫了,是不是我的哥? ,,,
好了, 話題就給你點到這, 再多說你的智商也理解不了了!
面向程序 VS 面向物件
面向程序的程式設計的核心是程序(流水線式思維),程序即解決問題的步驟,面向程序的設計就好比精心設計好一條流水線,考慮周全什么時候處理什么東西,
優點是:極大的降低了寫程式的復雜度,只需要順著要執行的步驟,堆疊代碼即可,
缺點是:一套流水線或者流程就是用來解決一個問題,代碼牽一發而動全身,
應用場景:一旦完成基本很少改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等,
面向物件的程式設計的核心是物件(上帝式思維),要理解物件為何物,必須把自己當成上帝,上帝眼里世間存在的萬物皆為物件,不存在的也可以創造出來,面向物件的程式設計好比如來設計西游記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題需要四個人:唐僧,沙和尚,豬八戒,孫悟空,每個人都有各自的特征和技能(這就是物件的概念,特征和技能分別對應物件的屬性和方法),然而這并不好玩,于是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經路上被搞死,又安排了一群神仙保駕護航,這些都是物件,然后取經開始,師徒四人與妖魔鬼怪神仙互相纏斗著直到最后取得真經,如來根本不會管師徒四人按照什么流程去取,
面向物件的程式設計的
優點是:解決了程式的擴展性,對某一個物件單獨修改,會立刻反映到整個體系中,如對游戲中一個人物引數的特征和技能修改都很容易,
缺點:可控性差,無法向面向程序的程式設計流水線式的可以很精準的預測問題的處理流程與結果,面向物件的程式一旦開始就由物件之間的互動解決問題,即便是上帝也無法預測最終結果,于是我們經常看到一個游戲人某一引數的修改極有可能導致陰霸的技能出現,一刀砍死3個人,這個游戲就失去平衡,
應用場景:需求經常變化的軟體,一般需求的變化都集中在用戶層,互聯網應用,企業內部軟體,游戲等都是面向物件的程式設計大顯身手的好地方,
在python 中面向物件的程式設計并不是全部,
面向物件編程可以使程式的維護和擴展變得更簡單,并且可以大大提高程式開發效率 ,另外,基于面向物件的程式可以使它人更加容易理解你的代碼邏輯,從而使團隊開發變得更從容,
了解一些名詞:類、物件、實體、實體化
類:具有相同特征的一類事物(人、狗、老虎)
物件/實體:具體的某一個事物(隔壁阿花、樓下旺財)
實體化:類——>物件的程序(這在生活中表現的不明顯,我們在后面再慢慢解釋)
初識類和物件
python中一切皆為物件,型別的本質就是類,所以,不管你信不信,你已經使用了很長時間的類了
>>> dict #型別dict就是類dict
<class 'dict'>
>>> d=dict(name='eva') #實體化
>>> d.pop('name') #向d發一條訊息,執行d的方法pop
'eva'
從上面的例子來看,字典就是一類資料結構,我一說字典你就知道是那個用{}表示,里面由k-v鍵值對的東西,它還具有一些增刪改查的方法,但是我一說字典你能知道字典里具體存了哪些內容么?不能,所以我們說對于一個類來說,它具有相同的特征屬性和方法,
而具體的{'name':'eva'}這個字典,它是一個字典,可以使用字典的所有方法,并且里面有了具體的值,它就是字典的一個物件,物件就是已經實實在在存在的某一個具體的個體,
再舉一個其他的例子,通俗一點,比如你現在有一個動物園,你想描述這個動物園,那么動物園里的每一種動物就是一個類,老虎、天鵝、鱷魚、熊,他們都有相同的屬性,比如身高體重出生時間和品種,還有各種動作,比如鱷魚會游泳,天鵝會飛,老虎會跑,熊會吃,
但是這些老虎熊啥的都不是具體的某一只,而是一類動物,雖然他們都有身高體重,但是你卻沒有辦法確定這個值是多少,如果這個時候給你一只具體的老虎,而你還沒死,那你就能給他量量身高稱稱體重,這些數值是不是就變成具體的了?那么具體的這一只老虎就是一個具體的實體,也是一個物件,不止這一只,其實每一只具體的老虎都有自己的身高體重,那么每一只老虎都是老虎類的一個物件,
在python中,用變數表示特征,用函式表示技能,因而具有相同特征和技能的一類事物就是‘類’,物件是則是這一類事物中具體的一個,
類的相關知識
初識類
宣告
def functionName(args):
'函式檔案字串'
函式體
'''
class 類名:
'類的檔案字串'
類體
'''
#我們創建一個類
class Data:
pass
class Person: #定義一個人類
role = 'person' #人的角色屬性都是人
def walk(self): #人都可以走路,也就是有一個走路方法,也叫動態屬性
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__()就會回傳一個物件,這個物件類似一個字典,存著屬于這個人本身的一些屬性和方法,
查看屬性&呼叫方法
print(egg.name) #查看屬性直接 物件名.屬性名
print(egg.walk()) #呼叫方法,物件名.方法名()
關于self
self:在實體化時自動將物件/實體本身傳給__init__的第一個引數,你也可以給他起個別的名字,但是正常人都不會這么做,
因為你瞎改別人就不認識
類屬性的補充
一:我們定義的類的屬性到底存到哪里了?有兩種方式查看
dir(類名):查出的是一個名字串列
類名.__dict__:查出的是一個字典,key為屬性名,value為屬性值
二:特殊的類屬性
類名.__name__# 類的名字(字串)
類名.__doc__# 類的檔案字串
類名.__base__# 類的第一個父類(在講繼承時會講)
類名.__bases__# 類所有父類構成的元組(在講繼承時會講)
類名.__dict__# 類的字典屬性
類名.__module__# 類定義所在的模塊
類名.__class__# 實體對應的類(僅新式類中)
物件的相關知識
回到咱們的人狗大戰:現在我們需要對我們的類做出一點點改變
人類除了可以走路之外,還應該具備一些攻擊技能,
class Person: # 定義一個人類
role = 'person' # 人的角色屬性都是人
def __init__(self, name, aggressivity, life_value):
self.name = name # 每一個角色都有自己的昵稱;
self.aggressivity = aggressivity # 每一個角色都有自己的攻擊力;
self.life_value = https://www.cnblogs.com/Golanguage/p/life_value # 每一個角色都有自己的生命值;
def attack(self,dog):
# 人可以攻擊狗,這里的狗也是一個物件,
# 人攻擊狗,那么狗的生命值就會根據人的攻擊力而下降
dog.life_value -= self.aggressivity
物件是關于類而實際存在的一個例子,即實體
物件/實體只有一種作用:屬性參考
egg = Person('egon',10,1000)
print(egg.name)
print(egg.aggressivity)
print(egg.life_value)
當然了,你也可以參考一個方法,因為方法也是一個屬性,只不過是一個類似函式的屬性,我們也管它叫動態屬性,
參考動態屬性并不是執行這個方法,要想呼叫方法和呼叫函式是一樣的,都需要在后面加上括號
print(egg.attack)
我知道在類里說,你可能還有好多地方不能理解,那我們就用函式來解釋一下這個類呀,物件呀到底是個啥,你偷偷的用這個理解就好了,不要告訴別人
def Person(*args,**kwargs):
self = {}
def attack(self,dog):
dog['life_value'] -= self['aggressivity']
def __init__(name,aggressivity,life_value):
self['name'] = name
self['aggressivity'] = aggressivity
self['life_value'] = life_value
self['attack'] = attack
__init__(*args,**kwargs)
return self
egg = Person('egon',78,10)
print(egg['name'])
面向物件小結——定義及呼叫的固定模式
class 類名:
def __init__(self,引數1,引數2):
self.物件的屬性1 = 引數1
self.物件的屬性2 = 引數2
def 方法名(self):pass
def 方法名2(self):pass
物件名 = 類名(1,2) #物件就是實體,代表一個具體的東西
#類名() : 類名+括號就是實體化一個類,相當于呼叫了__init__方法
#括號里傳引數,引數不需要傳self,其他與init中的形參一一對應
#結果回傳一個物件
物件名.物件的屬性1 #查看物件的屬性,直接用 物件名.屬性名 即可
物件名.方法名() #呼叫類中的方法,直接用 物件名.方法名() 即可
練習一:在終端輸出如下信息
小明,10歲,男,上山去砍柴
小明,10歲,男,開車去東北
小明,10歲,男,最愛大保健
老李,90歲,男,上山去砍柴
老李,90歲,男,開車去東北
老李,90歲,男,最愛大保健
老張…
物件之間的互動
現在我們已經有一個人類了,通過給人類一些具體的屬性我們就可以拿到一個實實在在的人,
現在我們要再創建一個狗類,狗就不能打人了,只能咬人,所以我們給狗一個bite方法,
有了狗類,我們還要實體化一只實實在在的狗出來,
然后人和狗就可以打架了,現在我們就來讓他們打一架吧!
創建一個狗類
class Dog: # 定義一個狗類
role = 'dog' # 狗的角色屬性都是狗
def __init__(self, name, breed, aggressivity, life_value):
self.name = name # 每一只狗都有自己的昵稱;
self.breed = breed # 每一只狗都有自己的品種;
self.aggressivity = aggressivity # 每一只狗都有自己的攻擊力;
self.life_value = https://www.cnblogs.com/Golanguage/p/life_value # 每一只狗都有自己的生命值;
def bite(self,people):
# 狗可以咬人,這里的狗也是一個物件,
# 狗咬人,那么人的生命值就會根據狗的攻擊力而下降
dog.life_value -= self.aggressivit
實體化一只實實在在的二哈
ha2 = Dog('二愣子','哈士奇',10,1000) #創造了一只實實在在的狗ha2
互動 egon打ha2一下
print(ha2.life_value) #看看ha2的生命值
egg.attack(ha2) #egg打了ha2一下
print(ha2.life_value) #ha2掉了10點血
完整的代碼
class Person: # 定義一個人類 role = 'person' # 人的角色屬性都是人 def __init__(self, name, aggressivity, life_value): self.name = name # 每一個角色都有自己的昵稱; self.aggressivity = aggressivity # 每一個角色都有自己的攻擊力; self.life_value = https://www.cnblogs.com/Golanguage/p/life_value # 每一個角色都有自己的生命值; def attack(self,dog): # 人可以攻擊狗,這里的狗也是一個物件, # 人攻擊狗,那么狗的生命值就會根據人的攻擊力而下降 dog.life_value -= self.aggressivity class Dog: # 定義一個狗類 role = 'dog' # 狗的角色屬性都是狗 def __init__(self, name, breed, aggressivity, life_value): self.name = name # 每一只狗都有自己的昵稱; self.breed = breed # 每一只狗都有自己的品種; self.aggressivity = aggressivity # 每一只狗都有自己的攻擊力; self.life_value = https://www.cnblogs.com/Golanguage/p/life_value # 每一只狗都有自己的生命值; def bite(self,people): # 狗可以咬人,這里的狗也是一個物件, # 狗咬人,那么人的生命值就會根據狗的攻擊力而下降 people.life_value -= self.aggressivity egg = Person('egon',10,1000) #創造了一個實實在在的人egg ha2 = Dog('二愣子','哈士奇',10,1000) #創造了一只實實在在的狗ha2 print(ha2.life_value) #看看ha2的生命值 egg.attack(ha2) #egg打了ha2一下 print(ha2.life_value) #ha2掉了10點血egon大戰哈士奇
from math import pi class Circle: ''' 定義了一個圓形類; 提供計算面積(area)和周長(perimeter)的方法 ''' def __init__(self,radius): self.radius = radius def area(self): return pi * self.radius * self.radius def perimeter(self): return 2 * pi *self.radius circle = Circle(10) #實體化一個圓 area1 = circle.area() #計算圓面積 per1 = circle.perimeter() #計算圓周長 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,找不到則去類中找,類也找不到就找父類...最后都找不到就拋出例外
面向物件的組合用法
軟體重用的重要方式除了繼承之外還有另外一種方式,即:組合
組合指的是,在一個類中以另外一個類的物件作為資料屬性,稱為類的組合
class Weapon:
def prick(self, obj): # 這是該裝備的主動技能,扎死對方
obj.life_value -= 500 # 假設攻擊力是500
class Person: # 定義一個人類
role = 'person' # 人的角色屬性都是人
def __init__(self, name):
self.name = name # 每一個角色都有自己的昵稱;
self.weapon = Weapon() # 給角色系結一個武器;
egg = Person('egon')
egg.weapon.prick()
#egg組合了一個武器的物件,可以直接egg.weapon來使用組合類中的所有方法
圓環是由兩個圓組成的,圓環的面積是外面圓的面積減去內部圓的面積,圓環的周長是內部圓的周長加上外部圓的周長,
這個時候,我們就首先實作一個圓形類,計算一個圓的周長和面積,然后在"環形類"中組合圓形的實體作為自己的屬性來用
from math import pi
class Circle:
'''
定義了一個圓形類;
提供計算面積(area)和周長(perimeter)的方法
'''
def __init__(self,radius):
self.radius = radius
def area(self):
return pi * self.radius * self.radius
def perimeter(self):
return 2 * pi *self.radius
circle = Circle(10) #實體化一個圓
area1 = circle.area() #計算圓面積
per1 = circle.perimeter() #計算圓周長
print(area1,per1) #列印圓面積和周長
class Ring:
'''
定義了一個圓環類
提供圓環的面積和周長的方法
'''
def __init__(self,radius_outside,radius_inside):
self.outsid_circle = Circle(radius_outside)
self.inside_circle = Circle(radius_inside)
def area(self):
return self.outsid_circle.area() - self.inside_circle.area()
def perimeter(self):
return self.outsid_circle.perimeter() + self.inside_circle.perimeter()
ring = Ring(10,5) #實體化一個環形
print(ring.perimeter()) #計算環形的周長
print(ring.area()) #計算環形的面積
用組合的方式建立了類與組合的類之間的關系,它是一種‘有’的關系,比如教授有生日,教授教python課程
class BirthDate:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
class Couse:
def __init__(self,name,price,period):
self.name=name
self.price=price
self.period=period
class Teacher:
def __init__(self,name,gender,birth,course):
self.name=name
self.gender=gender
self.birth=birth
self.course=course
def teach(self):
print('teaching')
p1=Teacher('egon','male',
BirthDate('1995','1','27'),
Couse('python','28000','4 months')
)
print(p1.birth.year,p1.birth.month,p1.birth.day)
print(p1.course.name,p1.course.price,p1.course.period)
'''
運行結果:
1 27
python 28000 4 months
'''
當類之間有顯著不同,并且較小的類是較大的類所需要的組件時,用組合比較好
初識面向物件小結
定義一個人類
class Person: # 定義一個人類
role = 'person' # 人的角色屬性都是人
def __init__(self, name, aggressivity, life_value, money):
self.name = name # 每一個角色都有自己的昵稱;
self.aggressivity = aggressivity # 每一個角色都有自己的攻擊力;
self.life_value = https://www.cnblogs.com/Golanguage/p/life_value # 每一個角色都有自己的生命值;
self.money = money
def attack(self,dog):
# 人可以攻擊狗,這里的狗也是一個物件,
# 人攻擊狗,那么狗的生命值就會根據人的攻擊力而下降
dog.life_value -= self.aggressivity
定義一個狗類
class Dog: # 定義一個狗類
role = 'dog' # 狗的角色屬性都是狗
def __init__(self, name, breed, aggressivity, life_value):
self.name = name # 每一只狗都有自己的昵稱;
self.breed = breed # 每一只狗都有自己的品種;
self.aggressivity = aggressivity # 每一只狗都有自己的攻擊力;
self.life_value = https://www.cnblogs.com/Golanguage/p/life_value # 每一只狗都有自己的生命值;
def bite(self,people):
# 狗可以咬人,這里的狗也是一個物件,
# 狗咬人,那么人的生命值就會根據狗的攻擊力而下降
people.life_value -= self.aggressivity
接下來,又創建一個新的兵器類,
class Weapon:
def __init__(self,name, price, aggrev, life_value):
self.name = name
self.price = price
self.aggrev = aggrev
self.life_value =https://www.cnblogs.com/Golanguage/p/ life_value
def update(self, obj): #obj就是要帶這個裝備的人
obj.money -= self.price # 用這個武器的人花錢買所以對應的錢要減少
obj.aggressivity += self.aggrev # 帶上這個裝備可以讓人增加攻擊
obj.life_value += self.life_value # 帶上這個裝備可以讓人增加生命值
def prick(self, obj): # 這是該裝備的主動技能,扎死對方
obj.life_value -= 500 # 假設攻擊力是500
測驗互動
lance = Weapon('長矛',200,6,100)
egg = Person('egon',10,1000,600) #創造了一個實實在在的人egg
ha2 = Dog('二愣子','哈士奇',10,1000) #創造了一只實實在在的狗ha2
#egg獨自力戰"二愣子"深感吃力,決定窮畢生積蓄買一把武器
if egg.money > lance.price: #如果egg的錢比裝備的價格多,可以買一把長矛
lance.update(egg) #egg花錢買了一個長矛防身,且自身屬性得到了提高
egg.weapon = lance #egg裝備上了長矛
print(egg.money,egg.life_value,egg.aggressivity)
print(ha2.life_value)
egg.attack(ha2) #egg打了ha2一下
print(ha2.life_value)
egg.weapon.prick(ha2) #發動武器技能
print(ha2.life_value) #ha2不敵狡猾的人類用武器取勝,血槽空了一半
按照這種思路一點一點的設計類和物件,最終你完全可以實作一個對戰類游戲,
使用types模塊確認方法和函式的區別
使用pickle存取自定義類的物件的方式
面向物件的三大特性
繼承
什么是繼承
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類
python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類 pass class 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.將人,豬,狗這三個類比較像的部分抽取成父類,
抽象最主要的作用是劃分類別(可以隔離關注點,降低復雜度)

繼承:是基于抽象的結果,通過編程語言去實作它,肯定是先經歷抽象這個程序,才能通過繼承的方式去表達出抽象的結構,
抽象只是分析和設計的程序中,一個動作或者說一種技巧,通過抽象可以得到類

繼承與重用性
==========================第一部分 例如 貓可以:吃、喝、爬樹 狗可以:吃、喝、看家 如果我們要分別為貓和狗創建一個類,那么就需要為 貓 和 狗 實作他們所有的功能,偽代碼如下: #貓和狗有大量相同的內容 class 貓: def 吃(self): # do something def 喝(self): # do something def 爬樹(self): # do something class 狗: def 吃(self): # do something def 喝(self): # do something def 看家(self): #do something ==========================第二部分 上述代碼不難看出,吃、喝是貓和狗都具有的功能,而我們卻分別的貓和狗的類中撰寫了兩次,如果使用 繼承 的思想,如下實作: 動物:吃、喝 貓:爬樹(貓繼承動物的功能) 狗:看家(狗繼承動物的功能) 偽代碼如下: class 動物: def 吃(self): # do something def 喝(self): # do something # 在類后面括號中寫入另外一個類名,表示當前類繼承另外一個類 class 貓(動物): def 爬樹(self): print '喵喵叫' # 在類后面括號中寫入另外一個類名,表示當前類繼承另外一個類 class 狗(動物): def 看家(self): print '汪汪叫' ==========================第三部分 #繼承的代碼實作 class Animal: def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '貓' def climb(self): print('爬樹') class Dog(Animal): def __init__(self, name): self.name = name self.breed='狗' def look_after_house(self): print('汪汪叫') # ######### 執行 ######### c1 = Cat('小白家的小黑貓') c1.eat() c2 = Cat('小黑的小白貓') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat()使用繼承來解決代碼重用的例子
在開發程式的程序中,如果我們定義了一個類A,然后又想新建立另外一個類B,但是類B的大部分內容與類A的相同時
我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念,
通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(資料屬性和函式屬性),實作代碼重用
class Animal:
'''
人和狗都是動物,所以創造一個Animal基類
'''
def __init__(self, name, aggressivity, life_value):
self.name = name # 人和狗都有自己的昵稱;
self.aggressivity = aggressivity # 人和狗都有自己的攻擊力;
self.life_value = https://www.cnblogs.com/Golanguage/p/life_value # 人和狗都有自己的生命值;
def eat(self):
print('%s is eating'%self.name)
class Dog(Animal):
pass
class Person(Animal):
pass
egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
egg.eat()
ha2.eat()
提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟體中的一部分設定大部分,大大生了編程作業量,這就是常說的軟體重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定制新的資料型別,這樣就是大大縮短了軟體開發周期,對大型軟體開發來說,意義重大.
派生
當然子類也可以添加自己新的屬性或者在自己這里重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那么呼叫新增的屬性時,就以自己為準了,
class Animal:
'''
人和狗都是動物,所以創造一個Animal基類
'''
def __init__(self, name, aggressivity, life_value):
self.name = name # 人和狗都有自己的昵稱;
self.aggressivity = aggressivity # 人和狗都有自己的攻擊力;
self.life_value = https://www.cnblogs.com/Golanguage/p/life_value # 人和狗都有自己的生命值;
def eat(self):
print('%s is eating'%self.name)
class Dog(Animal):
'''
狗類,繼承Animal類
'''
def bite(self, people):
'''
派生:狗有咬人的技能
:param people:
'''
people.life_value -= self.aggressivity
class Person(Animal):
'''
人類,繼承Animal
'''
def attack(self, dog):
'''
派生:人有攻擊的技能
:param dog:
'''
dog.life_value -= self.aggressivity
egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
print(ha2.life_value)
print(egg.attack(ha2))
print(ha2.life_value)
注意:像ha2.life_value之類的屬性參考,會先從實體中找life_value然后去類中找,然后再去父類中找...直到最頂級的父類,
在子類中,新建的重名的函式屬性,在編輯函式內功能的時候,有可能需要重用父類中重名的那個函式功能,應該是用呼叫普通函式的方式,即:類名.func(),此時就與呼叫普通函式無異了,因此即便是self引數也要為其傳值.
在python3中,子類執行父類的方法也可以直接用super方法.
class A: def hahaha(self): print('A') class B(A): def hahaha(self): super().hahaha() #super(B,self).hahaha() #A.hahaha(self) print('B') a = A() b = B() b.hahaha() super(B,b).hahaha()幫你了解super
class Animal:
'''
人和狗都是動物,所以創造一個Animal基類
'''
def __init__(self, name, aggressivity, life_value):
self.name = name # 人和狗都有自己的昵稱;
self.aggressivity = aggressivity # 人和狗都有自己的攻擊力;
self.life_value = https://www.cnblogs.com/Golanguage/p/life_value # 人和狗都有自己的生命值;
def eat(self):
print('%s is eating'%self.name)
class Dog(Animal):
'''
狗類,繼承Animal類
'''
def __init__(self,name,breed,aggressivity,life_value):
super().__init__(name, aggressivity, life_value) #執行父類Animal的init方法
self.breed = breed #派生出了新的屬性
def bite(self, people):
'''
派生出了新的技能:狗有咬人的技能
:param people:
'''
people.life_value -= self.aggressivity
def eat(self):
# Animal.eat(self)
#super().eat()
print('from Dog')
class Person(Animal):
'''
人類,繼承Animal
'''
def __init__(self,name,aggressivity, life_value,money):
#Animal.__init__(self, name, aggressivity, life_value)
#super(Person, self).__init__(name, aggressivity, life_value)
super().__init__(name,aggressivity, life_value) #執行父類的init方法
self.money = money #派生出了新的屬性
def attack(self, dog):
'''
派生出了新的技能:人有攻擊的技能
:param dog:
'''
dog.life_value -= self.aggressivity
def eat(self):
#super().eat()
Animal.eat(self)
print('from Person')
egg = Person('egon',10,1000,600)
ha2 = Dog('二愣子','哈士奇',10,1000)
print(egg.name)
print(ha2.name)
egg.eat()
通過繼承建立了派生類與基類之間的關系,它是一種'是'的關系,比如白馬是馬,人是動物,
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師
>>> class Teacher:
... def __init__(self,name,gender):
... self.name=name
... self.gender=gender
... def teach(self):
... print('teaching')
...
>>>
>>> class Professor(Teacher):
... pass
...
>>> p1=Professor('egon','male')
>>> p1.teach()
teaching
抽象類與介面類
介面類
繼承有兩種用途:
一:繼承基類的方法,并且做出自己的改變或者擴展(代碼重用)
二:宣告某個子類兼容于某基類,定義一個介面類Interface,介面類中定義了一些介面名(就是函式名)且并未實作介面的功能,子類繼承介面類,并且實作介面中的功能
class Alipay:
'''
支付寶支付
'''
def pay(self,money):
print('支付寶支付了%s元'%money)
class Applepay:
'''
apple pay支付
'''
def pay(self,money):
print('apple pay支付了%s元'%money)
def pay(payment,money):
'''
支付函式,總體負責支付
對應支付的物件和要支付的金額
'''
payment.pay(money)
p = Alipay()
pay(p,200)
開發中容易出現的問題
class Alipay:
'''
支付寶支付
'''
def pay(self,money):
print('支付寶支付了%s元'%money)
class Applepay:
'''
apple pay支付
'''
def pay(self,money):
print('apple pay支付了%s元'%money)
class Wechatpay:
def fuqian(self,money):
'''
實作了pay的功能,但是名字不一樣
'''
print('微信支付了%s元'%money)
def pay(payment,money):
'''
支付函式,總體負責支付
對應支付的物件和要支付的金額
'''
payment.pay(money)
p = Wechatpay()
pay(p,200) #執行會報錯
介面初成:手動報例外:NotImplementedError來解決開發中遇到的問題
class Payment:
def pay(self):
raise NotImplementedError
class Wechatpay(Payment):
def fuqian(self,money):
print('微信支付了%s元'%money)
p = Wechatpay() #這里不報錯
pay(p,200) #這里報錯了
借用abc模塊來實作介面
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self,money):
pass
class Wechatpay(Payment):
def fuqian(self,money):
print('微信支付了%s元'%money)
p = Wechatpay() #不調就報錯了
實踐中,繼承的第一種含義意義并不很大,甚至常常是有害的,因為它使得子類與基類出現強耦合,
繼承的第二種含義非常重要,它又叫“介面繼承”,
介面繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個兼容介面,使得外部呼叫者無需關心具體細節,可一視同仁的處理實作了特定介面的所有物件”——這在程式設計上,叫做歸一化,
歸一化使得高層的外部使用者可以不加區分的處理所有介面兼容的物件集合——就好象linux的泛檔案概念一樣,所有東西都可以當檔案處理,不必關心它是記憶體、磁盤、網路還是螢屏(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然后做出針對性的設計:細致到什么程度,視需求而定),
依賴倒置原則:
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該應該依賴細節;細節應該依賴抽象,換言之,要針對介面編程,而不是針對實作編程
在python中根本就沒有一個叫做interface的關鍵字,上面的代碼只是看起來像介面,其實并沒有起到介面的作用,子類完全可以不用去實作介面 ,如果非要去模仿介面的概念,可以借助第三方模塊:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py里使用zope.interface
檔案https://zopeinterface.readthedocs.io/en/latest/
設計模式:https://github.com/faif/python-patterns
介面提取了一群類共同的函式,可以把介面當做一個函式的集合,
然后讓子類去實作介面中的函式,
這么做的意義在于歸一化,什么叫歸一化,就是只要是基于同一個介面實作的類,那么所有的這些類產生的物件在使用時,從用法上來說都一樣,
歸一化,讓使用者無需關心物件的類是什么,只需要的知道這些物件都具備某些功能就可以了,這極大地降低了使用者的使用難度,
比如:我們定義一個動物介面,介面里定義了有跑、吃、呼吸等介面函式,這樣老鼠的類去實作了該介面,松鼠的類也去實作了該介面,由二者分別產生一只老鼠和一只松鼠送到你面前,即便是你分別不到底哪只是什么鼠你肯定知道他倆都會跑,都會吃,都能呼吸,
再比如:我們有一個汽車介面,里面定義了汽車所有的功能,然后由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實作了汽車介面,這樣就好辦了,大家只需要學會了怎么開汽車,那么無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函式呼叫)都一樣
為何要用介面
抽象類
什么是抽象類
與java一樣,python也有抽象類的概念但是同樣需要借助模塊實作,抽象類是一個特殊的類,它的特殊之處在于只能被繼承,不能被實體化
為什么要有抽象類
如果說類是從一堆物件中抽取相同的內容而來的,那么抽象類就是從一堆類中抽取相同的內容而來的,內容包括資料屬性和函式屬性,
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要么是吃一個具體的香蕉,要么是吃一個具體的桃子,,,,,,你永遠無法吃到一個叫做水果的東西,
從設計角度去看,如果類是從現實物件抽象而來的,那么抽象類就是基于類抽象而來的,
從實作角度來看,抽象類與普通類的不同之處在于:抽象類中有抽象方法,該類不能被實體化,只能被繼承,且子類必須實作抽象方法,這一點與介面有點類似,但其實是不同的,即將揭曉答案
在python中實作抽象類
#一切皆檔案 import abc #利用abc模塊實作抽象類 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定義抽象方法,無需實作功能 def read(self): '子類必須定義讀功能' pass @abc.abstractmethod #定義抽象方法,無需實作功能 def write(self): '子類必須定義寫功能' pass # class Txt(All_file): # pass # # t1=Txt() #報錯,子類沒有定義抽象方法 class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('文本資料的讀取方法') def write(self): print('文本資料的讀取方法') class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('硬碟資料的讀取方法') def write(self): print('硬碟資料的讀取方法') class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('行程資料的讀取方法') def write(self): print('行程資料的讀取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #這樣大家都是被歸一化了,也就是一切皆檔案的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)View Code
抽象類與介面類
抽象類的本質還是類,指的是一組類的相似性,包括資料屬性(如all_type)和函式屬性(如read、write),而介面只強調函式屬性的相似性,
抽象類是一個介于類和介面直接的一個概念,同時具備類和介面的部分特性,可以用來實作歸一化設計
在python中,并沒有介面類這種東西,即便不通過專門的模塊定義介面,我們也應該有一些基本的概念,
1.多繼承問題
在繼承抽象類的程序中,我們應該盡量避免多繼承;
而在繼承介面的時候,我們反而鼓勵你來多繼承介面
介面隔離原則:
使用多個專門的介面,而不使用單一的總介面,即客戶端不應該依賴那些不需要的介面,
2.方法的實作
在抽象類中,我們可以對一些抽象方法做出基礎實作;
而在介面類中,任何方法都只是一種規范,具體的功能需要子類實作
鉆石繼承
繼承順序

class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # def test(self): # print('from F') pass f1=F() f1.test() print(F.__mro__) #只有新式才有這個屬性可以查看線性串列,經典類沒有這個屬性 #新式類繼承順序:F->D->B->E->C->A #經典類繼承順序:F->D->B->A->E->C #python3中統一都是新式類 #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.方法的實作
在抽象類中,我們可以對一些抽象方法做出基礎實作;
而在介面類中,任何方法都只是一種規范,具體的功能需要子類實作
鉆石繼承
新式類:廣度優先
經典類:深度優先
多型
多型指的是一類事物有多種形態
動物有多種形態:人,狗,豬
import abc
class Animal(metaclass=abc.ABCMeta): #同一類事物:動物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #動物的形態之一:人
def talk(self):
print('say hello')
class Dog(Animal): #動物的形態之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #動物的形態之三:豬
def talk(self):
print('say aoao')
檔案有多種形態:文本檔案,可執行檔案
import abc
class File(metaclass=abc.ABCMeta): #同一類事物:檔案
@abc.abstractmethod
def click(self):
pass
class Text(File): #檔案的形態之一:文本檔案
def click(self):
print('open file')
class ExeFile(File): #檔案的形態之二:可執行檔案
def click(self):
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:序列型別有多種形態:字串,串列,元組,但他們直接沒有直接的繼承關系
#二者都像鴨子,二者看起來都像檔案,因而就可以當檔案一樣去用
class TxtFile:
def read(self):
pass
def write(self):
pass
class DiskFile:
def read(self):
pass
def write(self):
pass
封裝
【封裝】
隱藏物件的屬性和實作細節,僅對外提供公共訪問方式,
【好處】
1. 將變化隔離;
2. 便于使用;
3. 提高復用性;
4. 提高安全性;
【封裝原則】
1. 將不需要對外提供的內容都隱藏起來;
2. 把屬性都隱藏,提供公共方法對其訪問,
私有變數和私有方法
在python中用雙下劃線開頭的方式將屬性隱藏起來(設定成私有的)
私有變數
#其實這僅僅這是一種變形操作
#類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式:
class A:
__N=0 #類的資料屬性就應該是共享的,但是語法上是可以把類的資料屬性設定成私有的如__N,會變形為_A__N
def __init__(self):
self.__X=10 #變形為self._A__X
def __foo(self): #變形為_A__foo
print('from A')
def bar(self):
self.__foo() #只有在類內部才可以通過__foo的形式訪問到.
#A._A__N是可以訪問到的,即這種操作并不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形
這種自動變形的特點:
1.類中定義的__x只能在內部使用,如self.__x,參考的就是變形的結果,
2.這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的,
3.在子類定義的__x不會覆寫在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆寫的,
這種變形需要注意的問題是:
1.這種機制也并沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然后就可以訪問了,如a._A__N
2.變形的程序只在類的內部生效,在定義后的賦值操作,不會變形

私有方法
3.在繼承中,父類如果不想讓子類覆寫自己的方法,可以將方法定義為私有的
#正常情況
>>> class A:
... def fa(self):
... print('from A')
... def test(self):
... self.fa()
...
>>> class B(A):
... def fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from B
#把fa定義成私有的,即__fa
>>> class A:
... def __fa(self): #在定義時就變形為_A__fa
... print('from A')
... def test(self):
... self.__fa() #只會與自己所在的類為準,即呼叫_A__fa
...
>>> class B(A):
... def __fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from A
封裝與擴展性
封裝在于明確區分內外,使得類實作者可以修改封裝內的東西而不影響外部呼叫者的代碼;而外部使用用者只知道一個介面(函式),只要介面(函式)名、引數不變,使用者的代碼永遠無需改變,這就提供一個良好的合作基礎——或者說,只要介面這個基礎約定不變,則代碼改變不足為慮,
#類的設計者
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #對外提供的介面,隱藏了內部的實作細節,此時我們想求的是面積
return self.__width * self.__length
#使用者
>>> r1=Room('臥室','egon',20,20,20)
>>> r1.tell_area() #使用者呼叫介面tell_area
#類的設計者,輕松的擴展了功能,而類的使用者完全不需要改變自己的代碼
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #對外提供的介面,隱藏內部實作,此時我們想求的是體積,內部邏輯變了,只需求修該下列一行就可以很簡答的實作,而且外部呼叫感知不到,仍然使用該方法,但是功能已經變了
return self.__width * self.__length * self.__high
#對于仍然在使用tell_area介面的人來說,根本無需改動自己的代碼,就可以用上新功能
>>> r1.tell_area()
property屬性
什么是特性property
property是一種特殊的屬性,訪問它時會執行一段功能(函式)然后回傳值
例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便于理解) 成人的BMI數值: 過輕:低于18.5 正常:18.5-23.9 過重:24-27 肥胖:28-32 非常肥胖, 高于32 體質指數(BMI)=體重(kg)÷身高^2(m) EX:70kg÷(1.75×1.75)=22.86例一
class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def bmi(self): return self.weight / (self.height**2) p1=People('egon',75,1.85) print(p1.bmi)View Code
import math class Circle: def __init__(self,radius): #圓的半徑radius self.radius=radius @property def area(self): return math.pi * self.radius**2 #計算面積 @property def perimeter(self): return 2*math.pi*self.radius #計算周長 c=Circle(10) print(c.radius) print(c.area) #可以向訪問資料屬性一樣去訪問area,會觸發一個函式的執行,動態計算出一個值 print(c.perimeter) #同上 ''' 輸出結果: 314.1592653589793 62.83185307179586 '''例二:圓的周長和面積
為什么要用property
將一個類的函式定義成特性以后,物件再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函式然后計算出來的,這種特性的使用方式遵循了統一訪問的原則
除此之外,看下
ps:面向物件的封裝有三種方式:
【public】
這種其實就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什么大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開
【private】
這種封裝對誰都不公開
python并沒有在語法上把它們三個內建到自己的class機制中,在C++里一般會將所有的所有的資料都設定為私有的,然后提供set和get方法(介面)去設定和獲取,在python中通過property方法可以實作
class Foo:
def __init__(self,val):
self.__NAME=val #將所有的資料屬性都隱藏起來
@property
def name(self):
return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)
@name.setter
def name(self,value):
if not isinstance(value,str): #在設定值之前進行型別檢查
raise TypeError('%s must be str' %value)
self.__NAME=value #通過型別檢查后,將值value存放到真實的位置self.__NAME
@name.deleter
def name(self):
raise TypeError('Can not delete')
f=Foo('egon')
print(f.name)
# f.name=10 #拋出例外'TypeError: 10 must be str'
del f.name #拋出例外'TypeError: Can not delete'
一個靜態屬性property本質就是實作了get,set,delete三種方法
class Foo: @property def AAA(self): print('get的時候運行我啊') @AAA.setter def AAA(self,value): print('set的時候運行我啊') @AAA.deleter def AAA(self): print('delete的時候運行我啊') #只有在屬性AAA定義property后才能定義AAA.setter,AAA.deleter f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAAView Code
class Foo: def get_AAA(self): print('get的時候運行我啊') def set_AAA(self,value): print('set的時候運行我啊') def delete_AAA(self): print('delete的時候運行我啊') AAA=property(get_AAA,set_AAA,delete_AAA) #內置property三個引數與get,set,delete一一對應 f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAAView Code
怎么用?
class Goods:
def __init__(self):
# 原價
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
# 實際價格 = 原價 * 折扣
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deleter
def price(self):
del self.original_price
obj = Goods()
obj.price # 獲取商品價格
obj.price = 200 # 修改商品原價
print(obj.price)
del obj.price # 洗掉商品原價
classmethod
class Classmethod_Demo():
role = 'dog'
@classmethod
def func(cls):
print(cls.role)
Classmethod_Demo.func()
staticmethod
class Staticmethod_Demo():
role = 'dog'
@staticmethod
def func():
print("當普通方法用")
Staticmethod_Demo.func()
class Foo: def func(self): print('in father') class Son(Foo): def func(self): print('in son') s = Son() s.func() # 請說出上面一段代碼的輸出并解釋原因?練習1
class A: __role = 'CHINA' @classmethod def show_role(cls): print(cls.__role) @staticmethod def get_role(): return A.__role @property def role(self): return self.__role a = A() print(a.role) print(a.get_role()) a.show_role() # __role在類中有哪些身份? # 以上代碼分別輸出哪些內容? # 這三個裝飾器分別起了什么作用?有哪些區別?練習2
面向物件的更多說明
面向物件的軟體開發
很多人在學完了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.uj5u.com/houduan/194774.html
標籤:Python
上一篇:Python_包
下一篇:Python_面向物件進階
