__init__
首先明確一點,在面向對象以前咱們一直都是按照面向過程的方式來編寫程序!css
面向過程的程序設計:核心是過程二字,過程指的是解決問題的步驟,即先幹什麼再幹什麼......面向過程的設計就比如精心設計好一條流水線,是一種機械式的思惟方式。html
優勢是:複雜度的問題流程化,進而簡單化(一個複雜的問題,分紅一個個小的步驟去實現,實現小的步驟將會很是簡單)java
缺點是:一套流水線或者流程就是用來解決一個問題,生產汽水的流水線沒法生產汽車,即使是能,也得是大改,改一個組件,牽一髮而動全身。node
應用場景:一旦完成基本不多改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等。python
當今時代背景下,一般應用程序對擴展性和維護性要求都是很是高的,爲何?想一想qq,微信,是否是不斷的在添加新功能?,也就是說一款應用程序誕生後,都須要不斷的更新維護linux
面向對象編程——Object Oriented Programming,簡稱OOP,是一種程序設計思想。git
它將對象做爲程序的基本單元程序員
將數據和處理數據的程序封裝到對象中web
以提升軟件的重用性、靈活性和擴展性爲首要目的算法
案例分析1:把大象裝進冰箱如何實現
案例分析2:要開一家公司
面向對象編程的優勢:
缺點:
應用場景:
用一個例子來講明面向對象與面向過程的區別:
話說三國時期曹軍於官渡大敗袁紹,酒席之間,曹操詩興大發,吟道:喝酒唱歌,人生真爽! 衆將直呼:"丞相好詩",因而命印刷工匠刻板印刷以流傳天下;
待工匠刻板完成,交與曹操一看,曹操感受不妥,說道:"喝酒唱歌,此話太俗,應改成'對酒當歌'較好",因而名工匠從新刻板,當時尚未出現活字印刷術,若是樣板要改,只能從新刻板,工匠眼看連夜刻版之工,完全白費,心中叫苦連天。可也只得照辦。
版樣再次出來請曹操過目,曹操細細一品,以爲仍是很差,說」人生真爽太過直接,應該改問語纔夠意境,所以應改成‘對酒當歌,人生幾何?’「,因而....
在活字印刷術還沒出現以前,若是版樣有改動,只能從新雕刻。並且在印刷完成後,這個樣板就失去了它的價值,若是須要其餘樣板只能從新雕刻。而活字印刷術的出現就大大改善了印刷技術。如上例」喝酒唱歌,人生真爽「,若是用活字印刷,只須要改四個字就可,其他工做都未白作。豈不快哉!!
活字印刷也反應了OOP。當要改動時,只須要修改部分,此爲 可維護;當這些字用完後,並不是就徹底沒有價值了,它徹底能夠在後來的印刷中重複使用,此乃 可複用;此詩若要加字,只需另刻字加入便可,這就是 可擴展;字的排列能夠橫排,也能夠豎排,此是 靈活性好。
上述案列反應了OOP的優勢,便可維護性高,擴展性強,複用性高! 這些特色很是適用於用戶需求變化頻繁的互聯網應用程序,這是學習OOP的重要緣由
可是OOP設計的程序需涉及類與對象,相應的複雜度會提升!
並不是全部程序都須要較高的擴展性,例如系統內核,一旦編寫完成,基本不會再修改,使用面向過程來設計則更適用
類和對象是面向對象編程中最核心的兩個概念
對象是特徵與技能的結合體
如:演員張一山,姓名和職業是他的特徵,演戲是他的行爲,按照這樣的定義,生活中處處都是對象
在程序中:
用變量來表示對象的特徵,用函數表示對象的技能
將這些變量和函數結合在一塊兒,造成一個總體,就是對象,這是面向對象的精髓所在!
對象的另外一種理解方式
變量的做用數存儲數據,函數的做用數處理數據
對象是將數據與處理數據的函數綁定在一塊兒
類就是類型,類別,種類; 是一系列對象中類似特徵與技能的結合體
在生活中是一種抽象概念,例如人類,是不具體的
如某個對象屬於人類,能夠經過類別,瞭解這個對象具有的特徵和技能
反過來看類就是對象的模板,同一類的對象,具有相同的特徵和行爲
現實生活中,經過對對象的分析總結,獲得類型;用類型來標識不一樣對象之間的差別;
在程序中,一樣用於標識不一樣對象之間的差別
另外一個重要的功能是做爲對象的模板,例如學生類,不管是哪一個學生都具有學習這個方法,既然是相同的就不必爲每一個對象單獨編寫,學生類負責提供這些相同的方法;
OOP第一步要作的就是定義須要的類
以學生類Student爲例,在Python中,定義類經過class
關鍵字:
class Student:
pass
class
後面緊接着是類名,即Student
,遵循python編碼規範,類名一般是大寫開頭的單詞,多個單詞時使用駝峯命名法
建立對象也稱之爲實例化,定義好Student
類後,就能夠根據Student
類建立出Student
的實例,建立實例經過類名加上()實現:
xxxxxxxxxx
stu1 = Student()
print(stu1)
#輸出 <__main__.Student object at 0x10b11d588>
print(Student)
#輸出 <class '__main__.Student'>
根據輸出能夠看到
變量名stu1
指向一個Student
類的實例,0x10b11d588
是實例的內存地址,每一個實例的地址都不相同,
Student
自己則是一個類(class)
對象是特徵(屬性)與行爲(方法)的結合體
stu
這個對象目前不具有任屬性和方法,要爲其添加屬性能夠在建立對象後使用點語法(變量名加 . )
好比爲stu
對象添加name屬性
xxxxxxxxxx
stu1.name = "Jerry"
一樣經過點語法來獲取對象的屬性值
xxxxxxxxxx
print(stu1.name)
#輸出 Jerry
用於爲對象的屬性設置初始值的函數
在類的實例(對象)中,一些屬性是必須存在的,就可使用初始化函數來完成,好比Student
對象中的name
屬性,它是必須的,用於惟一標識一個學生
xxxxxxxxxx
class Student:
def __init__ (self,name):
print("init run")
self.name = name
執行過程:
在建立對象時Student("jack")
會申請新的內存空間用於保存對象數據,接着自動調init函數
注意:
__init__
函數要求第一個參數必須是self,該參數表示須要被初始化的對象自己,這樣就能夠將name屬性綁定到對象上
能夠將self改成其餘任意的名稱,但爲了保證易讀性一般是self,額外的參數須位於self以後
有了__init__
方法,在建立實例的時候,就不能傳入空的參數了,必須傳入與__init__
方法匹配的參數,但self
不須要傳,Python解釋器本身會把實例變量傳進去:
xxxxxxxxxx
# stu1 = Student()
# 以上代碼將拋出異常:TypeError: __init__() missing 1 required positional argument: 'name'
stu1 = Student("jack")
# 輸出 init run
print(stu1.name)
# 輸出 jack
小結:
能夠將類中的內容都稱之爲屬性,變量稱爲數據屬性,函數就叫函數屬性
類中能夠聲明變量來表示數據屬性,爲Student
類添加數據屬性和函數屬性
xxxxxxxxxx
class Student:
school = "Tsinghua" #數據屬性
def say_hello(self):#函數屬性
print("hello i am a student")
def __init__ (self,name): #初始化函數
self.name = name
也可使用點語法在建立對象後爲對象增長數據屬性
xxxxxxxxxx
stu = Student("Maria")
stu.age = 20
問題1:在類中聲明的數據屬性和建立對象後爲增長的數據屬性,有什麼區別?
類中的數據屬性是全部對象共享的
建立對象後爲增長的數據屬性,是這個對象特有的,去其餘對象無關
問題2:類中聲明的數據屬性和建立對象後爲增長的數據屬性,其訪問屬性是怎樣的?
優先查找對象本身的名稱空間,若是沒有則在類中找,若是類中也沒有則到父類中找,直到找到爲止,若是父類中也沒有則拋出異常
!!!注意!!!
此處父類可能也有父類,會一直沿着繼承關係查找到最終的父類Object,該繼承關係,後續會詳細討論!
案列:網頁中摺疊此處
stu1 = Student("Jack") stu2 = Student("Rose") #1.類中的數據屬性是全部對象共享的 print(stu1.school) print(stu2.school) #輸出 Tsinghua #輸出 Tsinghua #2.類中的數據屬性訪問的是同一塊內存 print(id(stu1.school)) print(id(stu2.school)) #輸出 4470412656 #輸出 4470412656 #3.類的函數屬性是綁定給對象使用的,bound method稱爲綁定方法,每一個對象的綁定方法內存地址不同 print(stu1.say_hello) print(stu2.say_hello) #輸出 <bound method Student.say_hello of <__main__.Student object at 0x10cc405f8>> #輸出 <bound method Student.say_hello of <__main__.Student object at 0x10cc40630>> #4.優先訪問對象本身的名稱空間 # 修改stu1的學習屬性爲北京大學 會在自stu1的名稱空間增長school屬性 stu1.school = "Beijing" print(stu1.__dict__) print(stu2.__dict__) #輸出 {'name': 'Jack', 'school': 'Beijing'} #輸出 {'name': 'Rose'} #4.1再次查看學校屬性 print(stu1.school) print(stu2.school) #輸出 Beijing #輸出 Tsinghua #__dict__用於訪問對象的名稱空間 本質是一個字典類型數據,存儲名稱與值的映射關係
屬性查找順序:對象->類->父類
先理清方法,函數,技能的關係:
生活中對象的技能在程序中用函數表示
函數在面向對象中稱之爲方法,換種稱呼而已!
如此說來,綁定方法也就是綁定函數
在使用面向對象以前,數據與處理數據的函數是獨立的沒有任何聯繫,在調用函數時須要手動傳入參數,若是要處理的數據有不少,參數的傳遞就是一個很是麻煩的事情,
原始的處理方式:函數 傳參
問題1 調用函數時傳入參數,若是要處理的數據有不少,編寫了不少重複的代碼,代碼的閱讀性不好
問題2 後期若是每次處理的數據個數變多了,函數須要修改參數列表,致使之前寫的全部代碼都須要修改,擴展性很是差
問題3 若是傳入了錯誤的數據,好比須要整型卻傳入了字符串,形成函數沒法正常工做
綁定方法的處理方式:
1.調用方法時傳入對象,對象中包含了須要的全部數據,減小重複代碼
2.後期數據變化時,修改類對象中的屬性,方法中增長相應的處理代碼,而方法參數不會發生變化,提升了擴展性
3.方法與對象進行綁定,沒有對象則沒法使用方法,而且在建立對象的初始化方法中,已經肯定了各個屬性數據時正確的,如此一來避免了傳入使用錯誤數據執行函數形成的問題
簡單的說,就是將數據與處理數據的函數綁定在一塊兒,沒有數據則根本不須要處理數據的函數,反過來要執行處理數據的函數則必須提供要被處理的數據
一:綁定方法
1.綁定到對象的方法:沒有被任何裝飾器裝飾的方法。
在類中定義的函數默認都是綁定到對象的方法
特色:參數的第一個必須是self 表示當前對象自己,使用對象來調用,調用時會自動傳入對象
2.綁定到類的方法:用classmethod裝飾器裝飾的方法。
特色:參數的第一個必須是cls表示當前類自己,使用類名來調用,調用時會自動傳入類
二:非綁定方法:用staticmethod裝飾器裝飾的方法
特色:不與類或對象綁定,類和對象均可以調用,可是沒有自動傳值那麼一說。就是一個普通函數
不過因爲做用域在類中因此須要使用類或對象類調用
class Student: school = "Tsinghua" def say_hello(self):# 綁定到對象的方法 print(self) print("hello i am a student my name is %s" % self.name) def __init__ (self,name): #綁定到對象的方法 self.name = name @classmethod # 綁定到類的方法 def school_info(cls): print(cls) print("the student from %s" % cls.school) stu1 = Student("Jack") print(stu1) #輸出 <__main__.Student object at 0x1063112e8> #1.調用對象綁定方法 stu1.say_hello() #輸出 <__main__.Student object at 0x1063112e8> #輸出 hello i am a student my name is Jack #查看對象綁定方法 print(stu1.say_hello) #輸出 <bound method Student.say_hello of <__main__.Student object at 0x10552b2e8>> #含義 這個綁定方法是Student類中的say_hello函數,綁定到地址爲0x10552b2e8的Student對象 #綁定方法本質上也是函數 只要能找到它就能調用它因此你能夠這樣來調用它 Student.say_hello(stu1) #輸出 <__main__.Student object at 0x103818390> #輸出 hello i am a student my name is Jack print(Student) #輸出 <class '__main__.Student'> #2.調用類綁定方法 Student.school_info() #輸出 <class '__main__.Student'> #輸出 the student from Tsinghua #查看類綁定方法 print(Student.school_info) #輸出 <bound method Student.school_info of <class '__main__.Student'>> #含義 這個綁定方法是Student類中的school_info函數,綁定到Student這個類
綁定到類的方法與綁定到對象的方法總結
異同點:
相同
綁定對象調用時都有自動傳參的效果
綁定到誰給誰就由誰來調用
不一樣
綁定到類的方法自動傳入當前類
綁定到對象的方法自動傳入當前對象
另外:
綁定方法中的self 和 cls參數名 是能夠隨意修改的,可是self和cls是約定俗成的寫法,爲了提升可讀性不建議修改
1.建立Student類
2.擁有如下屬性: 姓名 性別 年齡 學校 班級
3.擁有如下方法
save(name) 其做用是將這個對象序列化到文件中
get_obj(name)其做用是根據name從文件中反序列化爲獲得一個對象
分析save方法和get_obj 應該做爲綁定給對象仍是綁定給類
需求設計王者榮耀中的英雄類,每一個英雄對象能夠對其餘英雄對象使用技能
具有如下屬性
英雄名稱,等級,血量
和Q_hurt,W_hurt,E_hurt 三個屬性,表示各技能的傷害量
具有如下技能
Q W E
三個技能都須要一個敵方英雄做爲參數,當敵方血量小於等於0時角色死亡
代碼實現:
封裝指的是隱藏對象的屬性和實現細節,僅對外公開接口,控制程序中屬性的訪問權限;
python中的權限分爲兩種
1.公開 外界能夠直接訪問和修改
2.私有 外界不能直接訪問和修改,在當前類中能夠直接修改和訪問
一.封裝屬性
對於屬性而言,封裝就爲了限制屬性的訪問和修改,其目的是爲了保護數據安全
例如:
學生對象擁有,姓名,性別,年齡,和身份證號,分數;其中身份證是一個相對隱私的數據,不該該讓外界訪問到;
分數屬性,是一個很是關鍵的數據,決定學員能不能正常畢業,不該被隨意修改;
二.封裝方法
一個大的功能不少狀況下是由不少個小功能組合而成的,而這些內部的小功能對於用戶而言是沒有意義的,因此封裝方法的目的是爲了隔離複雜度;
例如:
電腦的開機功能,內部須要啓動BIOS,讀取系統配置,啓動硬盤,載入操做系統,等等一系列複雜的操做,可是用戶不須要關心這些實現邏輯,只要按下開機鍵等待開機便可;
在屬性名前添加兩個下劃線__
,將其設置爲私有的
1.封裝數據屬性實例:網頁中摺疊
class Student: def __init__(self,name,gender,age,id,score): # 初始化函數 self.name = name self.gender = gender self.age = age self.__id = id # 將id設置爲私有的 self.__score = score # 將score設置爲私有的 def test(self): print(self.__id) print(self.__score) stu = Student("Jack","man",20,"320684198901010001",780) #1.訪問私有屬性測試 #print(stu.id) # 直接訪問到隱私數據 #print(stu.__id) # 換種寫法 #以上兩行代碼均輸出類似的錯誤 #Traceback (most recent call last): # File "/Users/jerry/PycharmProjects/備課/寫課件/test.py", line 102, in <module> # print(stu.id) #AttributeError: 'Student' object has no attribute 'id' #錯誤含義 在Student類的對象中沒有一個id或__id屬性 #2.修改私有屬性測試 stu.score = 1 # 直接修改私有屬性 因爲語法特色,至關於給stu對象增長score屬性 stu.__score = 2 # 直接修改私有屬性 因爲語法特色,至關於給stu對象增長__score屬性 print(stu.score) print(stu.__score) #輸出 1 #輸出 2 #看起來已經被修改了 調用函數來查看私有屬性是否修改爲功 stu.test() #輸出 320684198901010001 #輸出 780 # 私有的數據沒有被修改過
思考:封裝能夠明確地區份內外,封裝的屬性能夠直接在內部使用,而不能被外部直接使用,然而定義屬性的目的終歸是要用,外部要想用類隱藏的屬性,須要爲其提供接口,讓外部可以間接地使用到隱藏起來的屬性,那這麼作的意義何在?
答:能夠在接口附加上對該數據操做的限制,以此完成對數據屬性操做的嚴格控制。
class Teacher: def __init__(self,name,age): # self.__name=name # self.__age=age self.set_info(name,age) def tell_info(self): print('姓名:%s,年齡:%s' %(self.__name,self.__age)) def set_info(self,name,age): if not isinstance(name,str): raise TypeError('姓名必須是字符串類型') if not isinstance(age,int): raise TypeError('年齡必須是整型') self.__name=name self.__age=age t=Teacher('egon',18) t.tell_info() t.set_info('egon',19) t.tell_info()
2.封裝函數屬性實例:網頁中摺疊
#取款是功能,而這個功能有不少功能組成:插卡、密碼認證、輸入金額、打印帳單、取錢 #對使用者來講,只須要知道取款這個功能便可,其他功能咱們均可以隱藏起來 #這麼作即隔離了複雜度,同時也提高了安全性 class ATM: def __card(self): print('插卡') def __auth(self): print('用戶認證') def __input(self): print('輸入取款金額') def __print_bill(self): print('打印帳單') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()
#其實這僅僅這是一種變形操做且僅僅只在類定義階段發生變形 #類中全部雙下劃線開頭的名稱如__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是能夠訪問到的, #這種,在外部是沒法經過__x這個名字訪問到。 a = A() print(a.__dict__) #輸出 {'_A__X': 10} #定義運行階段的賦值操做 a.__Y = 1 print(a.__dict__) #輸出 {'_A__X': 10, '__Y': 1} __y並無發生變形 """ 變形原理總結: 1.這種機制也並無真正意義上限制咱們從外部直接訪問屬性,知道了類名和屬性名就能夠拼出名字:_類名__屬性,而後就能夠訪問了,如a._A__N,即這種操做並非嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形,主要用來限制外部的直接訪問。 2.變形的過程只在類的定義時發生一次,在定義後的賦值操做,不會變形 """
5.隱藏的函數不會被子類覆蓋
property是一個裝飾器,將一個方法假裝成普通屬性,其特殊之處在於,該方法會在修改屬性值時自動執行
與之對應的是setter與deleter裝飾器:
setter裝飾的方法會在修改屬性值時自動執行
deleter裝飾的方法會在刪除屬性值自動執行
當咱們將一個屬性設置爲私有以後,就沒法直接訪問它們了,須要爲其建立兩個方法,一個用於訪問,一個用於修改 ,可是對於使用者而言,私有的和普通都是屬性,然而一個能夠用點來訪問,用等號來修改,另外一個卻要調用函數來存取,這就違反了統一訪問原則
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('Jack') print(f.name) # 訪問property屬性 #輸出 Jack f.name="Rose" # 修改property屬性 拋出異常'TypeError: 10 must be str' del f.name # 刪除property屬性 拋出異常'TypeError: Can not delete'
總結:property的做用是避免使用普通屬性和私有屬性時的方式發生變化
什麼是繼承
爲何須要繼承
存在繼承後的屬性查找順序
派生
在子類重用父類方法
經典類與新式類
菱形繼承的問題
mro列表
繼承是一種關係,經過繼承關係,一個對象能夠直接使用另外一個對象擁有的內容,例如王思聰繼承王建林,王思聰就可使用王健林擁有的財產!
被繼承的一方稱之爲父,即王健林; 繼承的一方稱之爲子,即王思聰
OOP繼承描述的是兩個類之間的關係,經過繼承,一個類能夠直接使用另外一個類中已定義的方法和屬性;
被繼承的稱之爲父類或基類,繼承父類的類稱之爲子類;
在python3中建立類時必然繼承另外一個類,若是沒有顯式的指定父類,則默認繼承object類; object是根類 全部類都直接或間接的繼承object
1.減小代碼重複
2.爲多態提供必要的支持,(關於多態下節會詳細討論!)
在類名後面的括號中指定要繼承的父類名稱class 類名(父類名):
案例:在選課系統中,有老師和學生兩種角色,老師擁有姓名,性別,年齡,學生也擁有姓名,性別,年齡,使用面向對象編程思想,能夠將老師和學生定義爲兩個爲不一樣的類
class Teacher: def __init__(self,name,gender,age): self.name = name self.gender = gender self.age = age def say_hi(self): print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender)) class Student: def __init__(self,name,gender,age): self.name = name self.gender = gender self.age = age def say_hi(self): print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender)) #建立兩個對象 t1 = Teacher("Jack","man",20) t1.say_hi() s1 = Student("Maria","woman",20) s1.say_hi()
兩個類中的內容徹底一致,則能夠經過繼承來重用代碼
class Teacher: def __init__(self,name,gender,age): self.name = name self.gender = gender self.age = age def say_hi(self): print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender)) class Student(Teacher): #指定Teacher類繼承Student類 pass #建立兩個對象 t1 = Teacher("Jack","man",20) t1.say_hi() s1 = Student("Maria","woman",20) s1.say_hi()
繼承描述的是子類與父類之間的關係,在上面的例子中,Student
繼承Teacher
完成了代碼的重用,可是很明顯老師類不是學生類的父類,學生類也不屬於老師類,這樣的繼承關係在邏輯上是錯誤的;OOP的概念來自於現實世界,因此繼承應當遵循現實世界的邏輯;
如今暫且不考慮邏輯錯誤,來看這樣一個狀況:
Teacher
和Student
因爲存在相同的屬性,爲了減小重複代碼,讓兩個邏輯上沒有繼承關係的類,產生了繼承關係,若是後期Teacher
類中增長了教學的方法,因爲繼承關係的存在,學生類也會擁有教學的方法,這是不合理的;
應當將Teacher
與Student
中徹底相同的部分抽取出來,放到另外一個類中,並讓Teacher與Student
去繼承它,這個類稱之爲公共父類
,可是這個類與實際的業務需求是無關的在現實中也不實際存在,它的做用僅僅是存儲相同代碼以減小重複;這一過程咱們稱之爲抽象;
綜上所述,正確思路是:先抽象在繼承
# 抽取老師和學生的相同內容 造成一個新的類,做爲它們的公共父類 class Person: def __init__(self,name,gender,age): self.name = name self.gender = gender self.age = age def say_hi(self): print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender)) class Teacher(Person): #指定Teacher類繼承Person類 pass class Student(Person): #指定Student類繼承Person類 pass #建立兩個對象 t1 = Teacher("Jack","man",20) t1.say_hi() s1 = Student("Maria","woman",20) s1.say_hi()
抽象最主要的做用是劃分類別(能夠隔離關注點,下降複雜度),每一個類之幹本身的事情,多個類相同的事情交給父類來幹
繼承的另外一種使用場景:
在開發程序的過程當中,若是咱們定義了一個類A,而後又想新創建另一個類B,可是類B的大部份內容與類A的相同時
不須要從頭開始寫一個類B,這就用到了類的繼承的概念。
經過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的全部屬性(數據屬性和函數屬性),實現代碼重用
用已經有的類創建一個新的類,這樣就重用了已經有的軟件中的一部分甚至大部分,大大節省了編程工做量,這就是常說的軟件重用,不只能夠重用本身的類,也能夠繼承別人的,好比標準庫,來定製新的數據類型,大大縮短了軟件開發週期,對大型軟件開發來講,意義重大
一個類必然繼承另外一個類,被繼承的類也有可能繼承了其餘類,至關於C繼承B,B又繼承A
此時查找屬性的順序是:
對象自己的名稱空間 - > 類的名稱空間 -> 父類的名稱空間 -> 父類的父類名稱空間 ->...object類
會沿着繼承關係一直日後查找,直到找到爲止,因爲object是全部類的根類,因此若是找不着最後都會查找object類!
class Foo: def f1(self): print('Foo.f1') def f2(self): print('Foo.f2') self.f1() class Bar(Foo): def f1(self): print('Bar.f1') b=Bar() b.f1() #輸出 Bar.f1 b.f2() #輸出 Foo.f2
當父類提供的屬性沒法徹底知足子類的需求時,子類能夠增長本身的屬性或非法,或者覆蓋父類已經存在的屬性,此時子類稱之爲父類的派生類;
在子類中若是出現於父類相同的屬性名稱時,根據查找順序,優先使用子類中的屬性,這種行爲也稱爲覆蓋
從Person
類派生出來的Teacher
類
# 抽取老師和學生的相同內容 造成一個新的類,做爲它們的公共父類 class Person: def __init__(self,name,gender,age): self.name = name self.gender = gender self.age = age def say_hi(self): print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender)) class Teacher(Person): #指定Teacher類繼承Person類 # Teacher類從Person類中繼承到了say_hi方法 可是,老師打招呼時應當說出本身的職業是老師,因此須要 # 定義本身的不一樣的實現方式 def say_hi(self): print("hi i am a Teacher") #print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender)) #上一行代碼與父類中徹底相同,能夠直接調用父類提供的方法 Person.say_hi(self) # 建立Teacher對象 t1 = Teacher("Jack","man",20) t1.say_hi() #輸出 hi i am a Teacher # my name is Jack age is 20 gender is man
在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能須要重用父類中重名的那個函數功能,應該使用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,所以即使是self參數也要爲其傳值
不少狀況下 子類中的代碼與父類中僅有小部分不一樣,卻不得不在子類定義新的方法,這時候能夠在子類中調用父類已有的方法,來完成大部分工做,子類僅需編寫一小部分與父類不一樣的代碼便可
在子類中有兩種方式能夠重用父類中的代碼
1.使用類名直接調用 ,該方式與繼承沒有關係,即時沒有繼承關係,也能夠調用
2.使用super()
class Vehicle: #定義交通工具類 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('開動啦...') class Subway(Vehicle): #地鐵 def __init__(self,name,speed,load,power,line): #super(Subway,self) 就至關於實例自己 在python3中super()等同於super(Subway,self) super().__init__(name,speed,load,power) self.line=line def run(self): print('地鐵%s號線歡迎您' %self.line) super(Subway,self).run() class Mobike(Vehicle):#摩拜單車 pass line13=Subway('中國地鐵','180m/s','1000人/箱','電',13) line13.run()
即便沒有直接繼承關係,super仍然會按照mro繼續日後查找
而第一種方式明確指定了要到哪個類中去查找,找不到則直接拋出異常
#A沒有繼承B,可是A內super會基於C.mro()繼續日後找 class A: def test(self): super().test() class B: def test(self): print('from B') class C(A,B): pass c=C() c.test() #打印結果:from B print(C.mro()) #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
*當你使用super()函數時,Python會在MRO列表上繼續搜索下一個類。若是每一個重定義的方法統一使用super()並只調用它一次,那麼控制流最終會遍歷完整個MRO列表,每一個方法也只會被調用一次(注意注意注意:使用super調用的全部屬性,都是從MRO列表當前的位置日後找,千萬不要經過看代碼去找繼承關係,必定要看MRO列表)
軟件重用的重要方式除了繼承以外還有另一種方式,即:組合
組合指的是,在一個類中以另一個類的對象做爲數據屬性,稱爲類的組合
class Equip: #武器裝備類 def fire(self): print('release Fire skill') class Riven: #英雄Riven的類,一個英雄須要有裝備,於是須要組合Equip類 camp='Noxus' def __init__(self,nickname): self.nickname=nickname self.equip=Equip() #用Equip類產生一個裝備,賦值給實例的equip屬性 r1=Riven('銳雯雯') r1.equip.fire() #可使用組合的類產生的對象所持有的方法
組合與繼承都是有效地利用已有類的資源的重要方式。可是兩者的概念和使用場景皆不一樣,
1.繼承的方式
經過繼承創建了派生類與基類之間的關係,它是一種'是'的關係,好比白馬是馬,人是動物。
當類之間有不少相同的功能,提取這些共同的功能作成基類,用繼承比較好,好比老師是人,學生是人
2.組合的方式
用組合的方式創建了類與組合的類之間的關係,它是一種‘有’的關係,好比教授有生日,教授教python和linux課程,教授有學生s一、s二、s3...
在Java和C#中子類只能繼承一個父類,而Python中子類能夠同時繼承多個父類,如A(B,C,D)
若是繼承關係爲非菱形結構,則會按照先找B這一條分支,而後再找C這一條分支,最後找D這一條分支的順序直到找到咱們想要的屬性
若是繼承關係爲菱形結構,那麼屬性的查找方式有兩種,分別是:深度優先和廣度優先
這裏談到的廣度優先不是簡單的從左到右,像圖中標識的依然會按照深度一層一層上找,可是若是下一個要找的類與繼承列表中的其餘類存在相同父類(就像E
與 F
有共同父類G
),則不會查找公共父類,這一次深度查找結束,開始下一條查找路徑(C -> F
),
對於你定義的每個類,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.若是對下一個類存在兩個合法的選擇,選擇第一個父類
usb就是一種接口,電源插座也是接口,接口實際上是一套協議規範;
電腦提供USB接口,可使用任何遵循USB接口協議的設備,其餘設備只要按照USB協議的要求來設計產品,就可以被電腦使用,而電腦根本不須要關心這個設備具體是如何實現功能的
1.讓使用者無需關心對象的類是什麼,只須要的知道這些對象都具有某些功能就能夠了,這極大地下降了使用者的使用難度。
2.使得外部使用者能夠不加區分的處理全部接口兼容的對象
2.1:就好象linux的泛文件概念同樣,全部東西均可以當文件處理,沒必要關心它是內存、磁盤、網絡仍是屏幕(固然,對底層設計者,固然也能夠區分出「字符設備」和「塊設備」,而後作出針對性的設計:細緻到什麼程度,視需求而定)。
2.2:再好比:咱們有一個汽車接口,裏面定義了汽車全部的功能,而後由本田汽車的類,奧迪汽車的類,大衆汽車的類,他們都實現了汽車接口,這樣就好辦了,你們只須要學會了怎麼開汽車,那麼不管是本田,仍是奧迪,仍是大衆咱們都會開了,開的時候根本無需關心我開的是哪一類車,操做手法(函數調用)都同樣
在python中根本就沒有一個叫作interface的關鍵字,若是非要去模仿接口的概念
1.能夠藉助第三方模塊:
http://pypi.python.org/pypi/zope.interface
2.也可使用繼承來間接的實現接口
繼承的兩種用途
一:繼承基類的方法,而且作出本身的改變或者擴展(代碼重用);
二:聲明某個子類兼容於某基類,定義一個接口類(模仿java的Interface),接口類中定義了一些接口名(就是函數名)但並未實現具體的功能,子類繼承接口類,而且實現接口中的功能
class IOInterface:#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關鍵字來定義一個接口。 def read(self): #定接口函數read pass def write(self): #定義接口函數write pass class Txt(Interface): #文本,具體實現read和write def read(self): print('文本數據的讀取方法') def write(self): print('文本數據的讀取方法') class Sata(Interface): #磁盤,具體實現read和write def read(self): print('硬盤數據的讀取方法') def write(self): print('硬盤數據的讀取方法') class Process(Interface): def read(self): print('進程數據的讀取方法') def write(self): print('進程數據的讀取方法')
上面的代碼只是看起來像接口,可是子類徹底能夠不用去實現接口,沒有強制性的要求子類必須實現父類的方法,這就用到了抽象類
什麼叫作抽象?
不具體,不清晰的就是抽象的,當咱們知道某些對象具有一些功能,可是並不清楚這些功能是如何實現的,那對於咱們而言這個功能就是抽象的; 抽象類也同樣,若是這個類中的方法是不具體(沒有實現功能的代碼)的抽象的,那麼這個類也是抽象的;
抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化,且有存在沒有實現的方法;
抽象類能夠實現強制性要求子類必須實現父類聲明的方法,這樣一來只要一個類是這個抽象類的子類,那麼他必然實現了抽象類中的方法,對於使用者而言,只要知道抽象類中的方法,就能夠無差異的使用,這個抽象類的任何子類,大大下降了使用成本!
#_*_coding:utf-8_*_ #一切皆文件 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)
補充:
抽象類中既能夠包含抽象方法也能夠包含普通方法和屬性!
這和接口不一樣,接口僅僅是協議,因此接口中不該該包含任何具體的實現代碼!
多態指的是一類事物有多種形態
例如:
動物有多種形態:
人,狗,豬
在程序中多態指的是,不一樣對象能夠響應相同方法,並能夠有本身不一樣的實現方式
案例分析:
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') peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是動物,只要是動物確定有talk方法 #因而咱們能夠不用考慮它們三者的具體是什麼類型,而直接使用 peo.talk() dog.talk() pig.talk() #更進一步,咱們能夠定義一個統一的接口來使用 def func(obj): obj.talk() func(peo) func(dog) func(pig)
經過上述案列能夠直觀的體會到多態的好處,而且它並非一個新的知識點,python默認就是支持多態的
那麼多態的帶來的好處是什麼?
1.增長了程序的靈活性
以不變應萬變,不論對象變幻無窮,使用者都是同一種形式去調用,如func(animal)
2.增長了程序額可擴展性
經過繼承animal類建立了一個新的類,使用者無需更改本身的代碼,仍是用func(animal)去調用
class Cat(Animal): #動物的另一種形態:貓 def talk(self): print('say miao') def func(animal): #對於使用者來講,本身的代碼根本無需改動 animal.talk() cat1=Cat() #實例出一隻貓 func(cat1) #甚至連調用方式也無需改變,就能調用貓的talk功能 say miao ''' 這樣咱們新增了一個形態Cat,由Cat類產生的實例cat1,使用者能夠在徹底不須要修改本身代碼的狀況下。使用和人、狗、豬同樣的方式調用cat1的talk方法,即func(cat1) '''
繼承一章中指出,繼承爲多態提供了不要的支持,全部的動物 cat dog pig
它們都要先繼承Animal
類,這樣一來,才能保證,它們都能響應talk方法,不至於在調用時發生異常;
固然若是子類的設計者,徹底按照Animal中規定的內容去實現子類,即便沒有繼承關係的存在,使用者也同樣能夠像使用其餘對象同樣使用這個子類對象, 這須要設計者在設計實現類時更加謹慎!
Python崇尚鴨子類型,即‘若是看起來像、叫聲像並且走起路來像鴨子,那麼它就是鴨子’
python程序員一般根據這種標準來編寫程序。例如,若是想編寫現有對象的自定義版本,能夠繼承該對象
也能夠建立一個外觀和行爲像,但與它無任何關係的全新對象,後者一般用於保存程序組件的鬆耦合度。
例1:利用標準庫中定義的各類‘與文件相似’的對象,儘管這些對象的工做方式像文件,但他們沒有繼承內置文件對象的方法
#兩者都像鴨子,兩者看起來都像文件,於是就能夠當文件同樣去用 class TxtFile: def read(self): pass def write(self): pass class DiskFile: def read(self): pass def write(self): pass
例2:其實你們一直在享受着多態性帶來的好處,好比Python的序列類型有多種形態:字符串,列表,元組,多態性體現以下
#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)