Python--面向物件初識
Python基礎-初識面向物件
面向物件程式設計——Object Oriented Programming,簡稱OOP,是一種程式設計思想。OOP把物件作為程式的基本單元,一個物件包含了資料和操作資料的函式。
面向過程的程式設計把計算機程式視為一系列的命令集合,即一組函式的順序執行。為了簡化程式設計,面向過程把函式繼續切分為子函式,即把大塊函式通過切割成小塊函式來降低系統的複雜度。
而面向物件的程式設計把計算機程式視為一組物件的集合,而每個物件都可以接收其他物件發過來的訊息,並處理這些訊息,計算機程式的執行就是一系列訊息在各個物件之間傳遞。
在Python中,所有資料型別都可以視為物件,當然也可以自定義物件。自定義的物件資料型別就是面向物件中的類(Class)的概念。
面向過程 VS 面向物件
面向過程的程式設計的核心是(流水線式思維),過程即解決問題的步驟,面向過程的設計就好比精心設計好的一條流水線 ,考慮周全什麼時候處理什麼東西。
- 優點:極大的降低了寫程式的複雜度,只需要順著要執行的步驟,堆疊程式碼即可。
- 缺點:一套流水線或者流程就是用來解決一個問題,程式碼牽一髮而動全身。
- 應用場景:一旦完成基本很少改變的場景,著名的例子有Linux核心,git,以及Apache HTTP Server 等。
面向物件的程式設計的核心是物件(上帝式思維),要理解物件為何物,必須把自己當成上帝,上帝眼裡世間存在的萬物皆為物件,不存在的也可以創造出來。面向物件的程式設計好比如來設計西遊記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題需要四個人:唐僧,沙和尚,豬八戒,孫悟空,每個人都有各自的特徵和技能(這就是物件的概念,特徵和技能分別對應物件的屬性和方法),然而這並不好玩,於是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經路上被搞死,又安排了一群神仙保駕護航,這些都是物件。然後取經開始,師徒四人與妖魔鬼怪神仙互相纏鬥著直到最後取得真經。如來根本不會管師徒四人按照什麼流程去取。
- 優點:解決了程式的擴充套件性。對某一個物件單獨修改,會立刻反映到整個體系中,如對遊戲中一個人物引數的特徵和技能修改都很容易。
- 缺點:可控性差,無法向面向過程的程式設計流水線式的可以很精準的預測問題的處理流程與結果,面向物件的程式一旦開始就由物件之間的互動解決問題,即便是上帝也無法預測最終結果。於是我們經常看到一個遊戲人某一引數的修改極有可能導致陰霸的技能出現,一刀砍死3個人,這個遊戲就失去平衡。
- 應用場景:需求經常變化的軟體,一般需求的變化都集中在使用者層,網際網路應用,企業內部軟體,遊戲等都是面向物件的程式設計大顯身手的好地方。
在python 中面向物件的程式設計並不是全部。
面向物件程式設計可以使程式的維護和擴充套件變得更簡單,並且可以大大提高程式開發效率 ,另外,基於面向物件的程式可以使它人更加容易理解你的程式碼邏輯,從而使團隊開發變得更從容。
瞭解一些名詞:類、物件、例項、例項化
- 類:具有相同特徵的一類事物(人、狗、老虎)
- 物件/例項:具體的某一個事物(隔壁阿花、樓下旺財)
- 例項化:類——>物件的過程
示例說明:
示例說明面向過程和麵向物件在程式流程上的不同之處。
假設我們要處理學生的成績表,為了表示一個學生的成績,面向過程的程式可以用一個dict表示:
std1 = { 'name': 'Michael', 'score': 98 } std2 = { 'name': 'Bob', 'score': 81 }
而處理學生成績可以通過函式實現,比如列印學生的成績:
def print_score(std): print('%s: %s' % (std['name'], std['score']))
如果採用面向物件的程式設計思想,我們首選思考的不是程式的執行流程,而是Student
這種資料型別應該被視為一個物件,這個物件擁有name
和score
這兩個屬性(Property)。如果要列印一個學生的成績,首先必須創建出這個學生對應的物件,然後,給物件發一個print_score
訊息,讓物件自己把自己的資料打印出來。
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score))
給物件發訊息實際上就是呼叫物件對應的關聯函式,我們稱之為物件的方法(Method)。面向物件的程式寫出來就像這樣:
bart = Student('Bart Simpson', 59) lisa = Student('Lisa Simpson', 87) bart.print_score() lisa.print_score()
類相關的知識
申明
def functionName(args): '函式文件字串' 函式體 ''' class 類名: '類的文件字串' 類體 ''' #建立一個類 class Data: pass
注意:類名通常是大寫開頭的單詞
屬性
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) #引用人的走路方法,注意,這裡不是在呼叫
例項化的過程就是類——>物件的過程
語法:物件名 = 類名(引數)
p1 = Person()
slef
self:在例項化時自動將物件/例項本身傳給__init__的第一個引數,你也可以給他起個別的名字,但是長的帥的人都不會這麼做。
因為你瞎改別人就不認識
類屬性的補充
一:我們定義的類的屬性到底存到哪裡了?有兩種方式檢視 dir(類名):查出的是一個名字列表 類名.__dict__:查出的是一個字典,key為屬性名,value為屬性值 二:特殊的類屬性 類名.__name__# 類的名字(字串) 類名.__doc__# 類的文件字串 類名.__base__# 類的第一個父類(在講繼承時會講) 類名.__bases__# 類所有父類構成的元組(在講繼承時會講) 類名.__dict__# 類的字典屬性 類名.__module__# 類定義所在的模組 類名.__class__# 例項對應的類(僅新式類中)
物件相關的知識
- 例項化物件
格式:物件名 = 類名(引數列表) 注意:沒有引數,小括號也不能省略
訪問物件的屬性和方法
- 訪問屬性
格式:物件名.屬性名 賦值:物件名.屬性名 = 新值
- 訪問方法
格式:物件名.方法名(引數列表)
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歲,男,最愛大保健 ''' class Person(object): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def print_hooby(self, hobby): print ("%s, %s歲, %s, %s "%(self.name, self.age, self.sex, hobby)) p1 = Person("小明", 10, "男") p1.print_hooby("上山去砍柴") p1.print_hooby("開車去東北") p1.print_hooby("最愛大保健") p2 = Person("老李", 90, "男") p2.print_hooby("上山去砍柴") p2.print_hooby("開車去東北") p2.print_hooby("最愛大保健")練習題
# 計算一個類例項過多少個物件 class Count: count = 0 def __init__(self): Count.count = self.count + 1 a = Count() b = Count() c = Count() d = Count() print(Count.count)
from math import pi class Circle: """ 定義了一個圓形類; 提供計算面積(area)和計算周長(perimeter)的方法 """ def __init__(self, radius): self.radius = radius def area(self): return self.radius ** 2 * pi def perimeter(self): return self.radius * 2 * pi circle = Circle(3) # 例項化一個圓 area1 = circle.area() # 計算圓面積 per1 = circle.perimeter() # 計算圓周長 print(area1, per1) # 列印圓面積和周長一個簡單的例子理解面向物件
類名稱空間與物件的名稱空間
建立一個類就會建立一個類的名稱空間,用來儲存類中定義的所有名字,這些名字稱為類的屬性
類有兩種屬性:靜態屬性和動態屬性
- 靜態屬性就是直接在類中定義的變數
- 動態屬性就是定義在類中的方法
其中類的資料屬性是共享給所有物件的
class Person: name = "小白" def fun1(self): print(self.name) p1 = Person() p2 = Person() print(id(p1.name)) 2436828402064 print(id(Person.name)) 2436828402064
而類的動態屬性是繫結到所有物件的
class Person: name = "小白" def fun1(self): print(self.name) p1 = Person() p2 = Person() print(p1.fun1) <bound method Person.fun1 of <__main__.Person object at 0x000002375E4A8C18>> print(Person.fun1) <function Person.fun1 at 0x000002375E4AB8C8>
建立一個物件/例項就會建立一個物件/例項的名稱空間,存放物件/例項的名字,稱為物件/例項的屬性
查詢順序:
- 物件.屬性:先從物件空間找,如果找不到,再從類空間找,再找不到,再從父類找...
- 類名.屬性:先從本類空間找,如果找不到,再從父類找...
物件與物件之間是互相獨立的.
面向物件的組合用法
組合指的是,給一個類的物件封裝一個屬性,這個屬性是另一個類的物件
圓環是由兩個圓組成的,圓環的面積是外面圓的面積減去內部圓的面積。圓環的周長是內部圓的周長加上外部圓的周長。
這個時候,我們就首先實現一個圓形類,計算一個圓的周長和麵積。然後在"環形類"中組合圓形的例項作為自己的屬性來用
from math import pi class Circle: """ 定義了一個圓形類; 提供計算面積(area)和計算周長(perimeter)的方法 """ def __init__(self, radius): self.radius = radius def area(self): return self.radius ** 2 * pi def perimeter(self): return self.radius * 2 * pi circle = Circle(3) # 例項化一個圓 area1 = circle.area() # 計算圓面積 per1 = circle.perimeter() # 計算圓周長 print(area1, per1) # 列印圓面積和周長 class Ring: def __init__(self, radius_outside, radius_inside): """ :param radius_outside: 外圓的半徑 :param radius_inside: 內圓的半徑 """ self.outside_redius = Circle(radius_outside) self.inside_redius = Circle(radius_inside) def rin_area(self): " 計算圓環的面積,用外圓的面積 - 內圓的面積" return self.outside_redius.area() - self.inside_redius.area() def rin_perimeter(self): " 計算圓環的周長,用外圓的周長 + 內圓的周長" return self.outside_redius.perimeter() + self.inside_redius.perimeter() ring = Ring(9, 3) # 例項化一個圓環 rinArea = ring.rin_area() # 計算圓環的面積 rinPer = ring.rin_perimeter() # 計算圓環的周長 print(rinArea, rinPer) # 列印圓環的面積和周長
""" 模擬英雄聯盟寫一個遊戲人物的類. 要求: (1)建立一個 Game_role的類. (2) 構造方法中給物件封裝name,ad(攻擊力),hp(血量).三個屬性. (3) 建立一個attack方法,此方法是例項化兩個物件,互相攻擊的功能: 例: 例項化一個物件 蓋倫,ad為10, hp為100 例項化另個一個物件 劍豪 ad為20, hp為80 蓋倫通過attack方法攻擊劍豪,此方法要完成 '誰攻擊誰,誰掉了多少血, 還剩多少血'的提示功能. """ class Gamerole: def __init__(self, name, ad, hp): self.name = name self.ad = ad self.hp = hp def attack(self, p1): hp = p1.hp - self.ad print("%s攻擊了%s, %s掉了%s血, 還剩%s血" % (self.name, p1.name, p1.name, self.ad, hp)) gailun = Gamerole("蓋倫", 10, 100) jianhao = Gamerole("劍豪", 20, 80) gailun.attack(jianhao) # 新增武器進行攻擊:斧子,刀,槍,棍,棒..., # 版本一: # 程式碼不合理: 人物利用武器攻擊別人,你的動作發起者是人,而不是武器. class Gamerole: def __init__(self, name, ad, hp): self.name = name self.ad = ad self.hp = hp def attack(self, p1): hp = p1.hp - self.ad print("%s攻擊了%s, %s掉了%s血, 還剩%s血" % (self.name, p1.name, p1.name, self.ad, hp)) class Arms: def __init__(self, name, ad): self.name = name self.ad = ad def fight(self, p1, p2): hp = p2.hp - self.ad print("%s使用%s攻擊了%s, %s掉了%s血, 還剩下%s血" % (p1.name, self.name, p2.name, p2.name, self.ad, hp)) gailun = Gamerole("蓋倫", 10, 100) jianhao = Gamerole("劍豪", 20, 80) dabaodao = Arms("大寶刀", 20) dabaodao.fight(gailun, jianhao) # 版本二:通過組合來實現,讓程式碼合理 class Gamerole: def __init__(self, name, ad, hp): self.name = name self.ad = ad self.hp = hp def armament_weapon(self, aex): # 呼叫時傳入武器物件 self.aex = aex class Arms: def __init__(self, name, ad): self.name = name self.ad = ad def fight(self, p1, p2): hp = p2.hp - self.ad print("%s使用%s攻擊了%s, %s掉了%s血, 還剩下%s血" % (p1.name, self.name, p2.name, p2.name, self.ad, hp)) gailun = Gamerole("蓋倫", 10, 100) jianhao = Gamerole("劍豪", 20, 80) dabaodao = Arms("大寶刀", 20) gailun.armament_weapon(dabaodao) # 給蓋倫裝備了大寶刀這個武器 gailun.aex.fight(gailun, jianhao)組合小練習
面向物件三大特徵
面向物件有三大特性:繼承、封裝、和多型。
繼承
繼承: 單繼承,多繼承.
單繼承
繼承是一種建立新類的方式,在Python中,新建的類可以繼承一個或者多個父類,父類又稱為基類或超類,新建的類稱為派生類或子類
子類可以自動擁有父類中除了私有屬性外的其他所有內容。說⽩了, ⼉⼦可以隨便用爹的東西。但是朋友們, 一定要認清楚⼀個事情。必須先有爹, 後有⼉⼦。 順序不能亂,在Python中實現繼承非常簡單。在宣告類的時候, 在類名後⾯面新增一個小括號,把要繼承的類傳進去就可以完成繼承關係。
那麼什麼情況可以使用繼承呢?
單純的從程式碼層⾯上來看,當兩個類具有相同的功能或者特徵的時候,可以採用繼承的形式。提取一個父類,這個父類中編寫兩個類中相同的部分。然後兩個類分別去繼承這個類就可以了。這樣寫的好處是我們可以避免寫很多重複的功能和程式碼。
class Animal: def eat(self): print "%s 吃 " %self.name def drink(self): print "%s 喝 " %self.name def shit(self): print "%s 拉 " %self.name def pee(self): print "%s 撒 " %self.name class Cat(Animal): def __init__(self, name): self.name = name self.breed = '貓' def cry(self): print '喵喵叫' class Dog(Animal): def __init__(self, name): self.name = name self.breed = '狗' def cry(self): print '汪汪叫' # ######### 執行 ######### c1 = Cat('小白家的小黑貓') c1.eat() c2 = Cat('小黑的小白貓') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat()
綜合示例:
動物都有吃、喝、拉、撒共同屬性,各動物有各動物的方法. 定義一個動物類(Animal),定義一個鳥類(Bird),定義一個馬類(Horse)
class Animal(object): def __init__(self, name, sex, age): self.name = name self.sex = sex self.age = age def eat(self): print("%s 在吃東西" % self.name) def drink(self): print("%s 在喝東西" % self.name) def pull(self): print("%s 在拉粑粑" % self.name) def sow(self): print("%s 在噓噓" % self.name) class Bird(Animal): def flight(self): print("%s 在自由的飛翔" % self.name) class Horse(Animal): def running(self): print("%s 在飛馳的奔跑" % self.name) bird1 = Bird("麻雀", 1.2, "公") # 例項化一隻鳥 bird1.flight() # 呼叫自身獨有的方法 bird1.eat() # 呼叫父類公共的方法 horse1 = Horse("草原馬", 3, "公") # 例項化一匹馬 horse1.running() # 呼叫自身獨有的方法 horse1.pull() # 呼叫父類公共的方法
現在要給鳥類新增一個翅膀的屬性,給馬類新增一個尾巴的屬性
class Animal(object): def __init__(self, name, sex, age): self.name = name self.sex = sex self.age = age def eat(self): print("%s 在吃東西" % self.name) def drink(self): print("%s 在喝東西" % self.name) def pull(self): print("%s 在拉粑粑" % self.name) def sow(self): print("%s 在噓噓" % self.name) class Bird(Animal): def __init__(self, name, sex, age, wing): # 方法一:通過父類名.父類方法名(引數) # Animal.__init__(self, name, sex, age) # 方法二:通過super().父類方法名(引數(自動傳入self)) super().__init__(name, sex, age) # 還可以這樣寫super(Bird,self).__init(name, sex, age) self.wing = wing def flight(self): print("%s 在自由的飛翔" % self.name) class Horse(Animal): def __init__(self, name, sex, age, tail): # Animal.__init__(self, name, sex, age) super().__init__(name, sex, age) self.tail = tail def running(self): print("%s 在飛馳的奔跑" % self.name) bird2 = Bird("鸚鵡", 6, "母", "紅翅膀") # 例項化一隻鳥,加入翅膀 print(bird2.__dict__) # 檢視是否將翅膀屬性成功新增 # {'name': '鸚鵡', 'sex': 6, 'age': '母', 'wing': '紅翅膀'} horse2 = Horse("汗血馬", 3, "公", "長尾巴") # 例項化一匹馬,加入尾巴 print(horse2.__dict__) # 檢視是否將尾巴屬性成功新增 # {'name': '汗血馬', 'sex': 3, 'age': '公', 'tail': '長尾巴'}
總結:
只執行父類的方法:子類中不要定義與父類同名的方法
只執行子類的方法:在子類建立這個方法
問題:既要執行子類的方法,又要執行父類的方法?
有兩種解決方法:
1、在子類中執行父類的方法
- 父類名.父類方法名(引數)
2、通過super()
- super().父類方法名(引數(自傳self))
多繼承
類: 經典類, 新式類
- 新式類: 凡是繼承object類都是新式類. python3x 所有的類都是新式類,因為python3x中的類都預設繼承object.
class A(object): pass
- 經典類: 不繼承object類都是經典類. python2x:(既有新式類,又有經典類) 所有的類預設都不繼承object類,所有的類預設都是經典類.你可以讓其繼承object.
class A: pass
單繼承: 新式類,經典類查詢順序一樣
多繼承:
- 新式類: 遵循廣度優先. 廣度優先 : 一條路走到倒數第二級,判斷,如果其他路能走到終點,則返回走另一條路.如果不能,則走到終點.
- 經典類: 遵循深度優先. 深度優先 : 一條路走到底.
- 深度優先,廣度優先:只能是繼承兩個類的情況
經典類多繼承:
class A: def func(self): print(" IN A") class B(A): def func(self): print(" IN B") class C(A): def func(self): print(" IN C") class D(B): def func(self): print(" IN D") class E(C): def func(self): print(" IN E") class F(D, E): def func(self): print(" IN F") func1 = F() func1.func() # 執行func方法時: # 首先去F類找,如果F沒有去D類找,如果D類沒有去B類找,如果B類沒有去A類找,如果A類沒有去E類找,如果E類沒有去C類找,如果C類還沒有則報錯 # 查詢順序為F——>D——>B——>A——>E——>C # 在上述查詢func方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
新式類多繼承:
class A(object): def func(self): print(" IN A") class B(A): def func(self): print(" IN B") class C(A): def func(self): print(" IN C") class D(B): def func(self): print(" IN D") class E(C): def func(self): print(" IN E") class F(D, E): def func(self): print(" IN F") func1 = F() func1.func() print(F.mro()) # 查詢類的繼承順序 # 列印結果:[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] # 執行func方法時: # 首先去F類找,如果F沒有去D類找,如果D類沒有去B類找,如果B類沒有去E類找,如果E類沒有去C類找,如果C類沒有去A類找,如果A類還沒有則報錯 # 查詢順序為F——>D——>B——>E——>C——>A # 在上述查詢func方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
繼承總結
面向物件為什麼要有繼承?繼承的好處是什麼?
- 優化程式碼、節省程式碼
- 提高程式碼的複用性
- 提高程式碼的維護性
- 讓類與類之間發生關係
抽象類與介面類
介面類
為什麼要使用介面類?
介面提取了一群類共同的函式,可以把介面當做一個函式的集合。
然後讓子類去實現介面中的函式。
這麼做的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個介面實現的類,那麼所有的這些類產生的物件在使用時,從用法上來說都一樣。
歸一化的好處在於:
- 歸一化讓使用者無需關心物件的類是什麼,只需要知道這些物件都具備了某些功能就可以了,這極大的降低了使用者的使用難度。
- 歸一化使得高層的外部使用者可以不加區分的處理多有介面相容的物件集合。
比如:我們定義一個動物介面,接口裡定義了有跑、吃、呼吸等介面函式,這樣老鼠的類去實現了該介面,松鼠的類也去實現了該介面,由二者分別產生一隻老鼠和一隻松鼠送到你面前,即便是你分別不到底哪隻是什麼鼠你肯定知道他倆都會跑,都會吃,都能呼吸。 再比如:我們有一個汽車介面,裡面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車介面,這樣就好辦了,大家只需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函式呼叫)都一樣
比如
介面類示例
需求說明:開發一個支付功能、支援微信支付、支付寶支付、Apple支付等...
版本一:採用歸一化設計
# 版本一:採用歸一化設計 class Alypay(object): """ 支付寶支付 """ def pay(self, money): print("支付寶支付了 %s 元" % money) class Wechatpay(object): """ 微信支付 """ def pay(self, money): print("微信支付了 %s 元" % money) class Applepay(object): """ apple支付 """ def pay(self, money): print("apple支付了 %s 元" % money) def apy(obj, money): obj.pay(money) a1 = Alypay() w1 = Wechatpay() p1 = Applepay() apy(a1, 200) apy(w1, 100) apy(p1, 50)
按照上面這種方法是可行的, 但是有個問題, 如果第二個程式設計師去更改你的程式碼,如果類中不是定義的pay函式去支付, 那麼就會報錯,列如下面將apple支付裡面的函式(pay)變為(applepay).
版本二:
class Alypay(object): """ 支付寶支付 """ def pay(self, money): print("支付寶支付了 %s 元" % money) class Wechatpay(object): """ 微信支付 """ def pay(self, money): print("微信支付了 %s 元" % money) class Applepay(object): """ apple支付 """ def applepay(self, money): print("apple支付了 %s 元" % money) def apy(obj, money): obj.pay(money) a2 = Alypay() w2 = Wechatpay() p2 = Applepay() apy(a2, 200) apy(w2, 100) apy(p2, 50) # 這裡執行就會報錯. 打破了歸一化設計原則
版本三:通過abc模組來實現介面,如果類中定義的非模組指定的函式名,我們讓其報錯。
from abc import ABCMeta, abstractclassmethod class Payment(metaclass=ABCMeta): @abstractclassmethod def pay(self, money): pass class Alypay(Payment): """ 支付寶支付 """ def pay(self, money): print("支付寶支付了 %s 元" % money) class Wechatpay(Payment): """ 微信支付 """ def pay(self, money): print("微信支付了 %s 元" % money) class Applepay(Payment): """ apple支付 """ def Applepay(self, money): print("apple支付了 %s 元" % money) def apy(obj, money): obj.pay(money) a1 = Alypay() w1 = Wechatpay() p1 = Applepay() # 執行報錯: TypeError: Can't instantiate abstract class Applepay with abstract methods pay apy(a1, 200) apy(w1, 100) apy(p1, 50)
最終版本:
from abc import ABCMeta, abstractclassmethod class Payment(metaclass=ABCMeta): @abstractclassmethod def pay(self, money): pass class Alypay(Payment): """ 支付寶支付 """ def pay(self, money): print("支付寶支付了 %s 元" % money) class Wechatpay(Payment): """ 微信支付 """ def pay(self, money): print("微信支付了 %s 元" % money) class Applepay(Payment): """ apple支付 """ def pay(self, money): print("apple支付了 %s 元" % money) def apy(obj, money): obj.pay(money) a1 = Alypay() w1 = Wechatpay() p1 = Applepay() apy(a1, 200) apy(w1, 100) apy(p1, 50)
抽象類
- 什麼是抽象類?
抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被例項化; Python抽象類是藉助模組來實現
- 為啥要有抽象類?
如果說類是從一堆物件中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括資料屬性和函式屬性。
比如我們又香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。你永遠無法吃到一個叫做水果的東西。
從設計角度看,如果類是從現實物件抽取而來的,那麼抽象類就是基於類抽象而來的。
從實現角度看,抽象類與普通類的不同之處在於:抽象類中有抽象的方法,該類不能被例項化,只能被繼承,且子類必須實現抽象方法。這一點與介面類有點類似,但其實是不同的。
在Python中實現抽象類
# 一切皆檔案 import abc # 利用abc模組實現抽象類 class All_file(metaclass=abc.ABCMeta): all_type = 'file' @abc.abstractclassmethod # 定義抽象方法,無需實現功能 def read(self): '子類必須定義讀功能' pass @abc.abstractclassmethod # 定義抽象方法,無需實現功能 def write(self): '子類必須定義寫功能' pass # class Txt(All_file): # pass # # t1 = Txt() # 報錯,子類沒有定義抽象方法 # 報錯內容 TypeError: Can't instantiate abstract class Txt with abstract methods read, write 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("程序資料的寫方法") wenbenfile = Txt() satafile = Sata() processfile = Process() # 這樣大家都被歸一化了,也就是一切皆檔案的思想 wenbenfile.read() satafile.write() processfile.read() print(wenbenfile.all_type) print(satafile.all_type) print(processfile.all_type)
抽象類與介面類的總結
抽象類的本質還是類,指的是一組類的相似性,包括資料屬性(如all_type)和函式屬性(如read、write),而介面只強調函式屬性的相似性。
抽象類是一個介於類和介面直接的一個概念,同時具備類和介面的部分特性,可以用來實現歸一化設計
在python中,並沒有介面類這種東西,即便不通過專門的模組來定義介面,我們也應該有一些基本的概念。
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:利用標準庫中定義的各種‘與檔案類似’的物件,儘管這些物件的工作方式像檔案,但他們沒有繼承內建檔案物件的方法
class TxtFile(object): def read(self): pass def write(self): pass class DiskFile(object): def read(self): pass def write(self): pass例子
例2:序列型別有多種形態:字串,列表,元組,但他們直接沒有直接的繼承關係
class TxtFile(object): def read(self): pass def write(self): pass class DiskFile(object): def read(self): pass def write(self): pass # str, list, tuple 都是序列型別 s = str('hello') l = list([1, 2, 3]) t = tuple((4, 5, 6)) # 我們可以在不考慮三者型別的前提下使用s, l, t s.__len__() l.__len__() t.__len__() len(s) len(l) len(t)例子
封裝
封裝
- 隱藏物件的屬性和實現細節,僅對外提供公共訪問方式。
好處
- 將變化隔離;
- 便於使用;
- 提高複用性;
- 提高安全性;
封裝原則
- 將不需要對外提供的內容都隱藏起來;
- 把屬性都隱藏,提供公共方法對其訪問。
私有變數和私有方法
在Python中使用雙下劃線開頭的方式將屬性隱藏起來(設定為私有的)
私有變數
# 其實這僅僅只是一種變形操作 # 類中所有雙下劃線開頭的名稱如__x 都會自動形成:_類名__x的新式: class A(object): name = "小白" __age = 18 # 類的資料屬性就應該是共享的,但是語法上是可以把類的資料屬性設定成私有的如__age,會變形為_A__age def __init__(self): self.__x = 10 # 變形為self._A__X def __foo(self): # 變形為_A__foo print(self.__age) print("From A") def fun1(self): self.__foo() # 只有在類內部才可以通過__foo的形式訪問到. obj1 = A() print(obj1.name) # print(obj1.__age) # 執行報錯;因為例項化物件不能訪問私有變數 # print(A.__age) # 執行報錯;因為類名不能訪問私有變數 # 對於私有變數,類的外部不能訪問. obj1.fun1() # 對於私有靜態變數,類的內部可以訪問. print(A._A__age) # A._A__age是可以訪問到的,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形
對於私有變數來說,只能在本類中內部訪問,類的外部,派生類均不可訪問.
這種自動變形的特點:
1.類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果
2.這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。
3.在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。
這種變形需要注意的問題是:
1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了,如a._A__N
2.變形的過程只在類的內部生效,在定義後的賦值操作,不會變形
私有方法
在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的
class A(object): def func1(self): print(" In A ") def func2(self): self.func1() class B(A): def func1(self): print(" In B ") c1 = B() c1.func2() # 列印結果: In B class A(object): def __func1(self): print(" In A ") def fu