面向對象
面向過程與面向對象的對比:
面向過程:核心是過程二字,過程指的是問題的解決步驟,即先幹什麼再幹什麼,基於面向過程去設計程序就好比在設計一條流水線,是一種機械式的思維方式
優點:複雜的問題流程化,進而簡單化
缺點:可擴展性差
應用:腳本程序,比如linux系統管理腳本,著名案例:linux內核,httpd,git
面向對象:核心是對象二字,對象就是特徵與技能的結合體,如果把設計程序比喻成
創造一個世界,那你就是這個世界的上帝,與面向過程對機械流水的模擬形式鮮明的對比,面向對象更加註重的對現實時間的模擬。
優點:可擴展性,對某一個對象單獨修改,會立刻反映到整個體系中
缺點:
1. 編程的複雜度遠高於面向過程,不瞭解面向對象而立即上手基於它設計程序,極容易出現過度設計的問題。一些擴展性要求低的場景使用面向對象會徒增編程難度,比如管理linux系統的shell腳本就不適合用面向對象去設計,面向過程反而更加適合。
2. 無法像面向過程的程序設計流水線式的可以很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題,只有對象之間交互才能準確地知道最終的結果
應用場景:需求經常變化的軟件,一般需求的變化都集中在用戶層,互聯網應用,企業內部軟件,遊戲等都是面向對象的程序設計大顯身手的好地方
一.類與對象
對象是特徵與技能的結合體
類是一系列對象相似的特徵與技能的結合體
類有兩種屬性:數據屬性和函數屬性
1. 類的數據屬性是所有對象共享的
2. 類的函數屬性是綁定給對象用的
對象調用類的函數中的變量時,首先會從該函數的命名空間尋找變量,找不到則去類中找,再去父類中找,最後都找不到則拋出異常
函數命名空間 ---> 類的數據屬性 ---> 父類
Python中爲類內置的特殊屬性
類名.__name__ # 類的名字(字符串)
類名.__doc__ # 類的文檔字符串
類名.__base__ # 類的第一個父類
類名.__bases__ # 類所有父類構成的元組
類名.__dict__ # 類的字典屬性
類名.__module__ # 類定義所在的模塊
類名.__class__ # 實例對應的類(僅新式類中)
在程序中:
一定是先定義類,後調用類來產生對象
class OldboyStudent: # 定義類 school = 'oldboy' #類的數據屬性 def learn(self): #類的函數屬性 print('is learning') def eat(self): print('is eating') print('======>') # 類體的代碼在類定義階段就會執行,理所應當會產生類的名稱空間,用__dict__屬性查看 print(OldboyStudent.__dict__) print(OldboyStudent.__dict__['school']) print(OldboyStudent.__dict__['learn']) #產生程序中的對象:類名加括號,調用類,產生一個該類的實際存在的對象,該調用過程稱爲實例化,產生的結果又可以成爲實例 class OldboyStudent: school = 'oldboy' def __init__(self,name,age,sex): #在實例化時,產生對象之後執行 # if not isinstance(name,str): # 判斷如果name不是字符串類型 # raise TypeError # raise主動拋出異常 self.name=name self.age=age self.sex=sex # return None #__init__方法必須返回None # 綁定方法:綁定給誰,就由誰來調用,誰來調用就把“誰”本身當做第一個參數傳入,所以在類中定義函數,函數中的參數中必須要傳入一個self參數 def learn(self): print('is learning') def eat(self): print('is eating') obj1=OldboyStudent('李大炮',18,'女') # #分兩步: #第一步:先產生一個空對象obj1 #第二步:OldboyStudent.__init__(obj1,'李大炮',18,'女') print(obj1.__dict__) obj2=OldboyStudent('張全蛋',28,'男') obj3=OldboyStudent('牛榴彈',18,'女')
練習:
class Garen: #定義蓋倫的類 camp='Demacia' # 定義蓋倫的數據屬性 def __init__(self,nickname,life_value=100,aggresivity=80): self.nickname=nickname self.life_value=life_value self.aggresivity=aggresivity def attack(self,enemy): # ***函數 enemy.life_value-=self.aggresivity #被***對象的生命值減去己方的***力 class Riven: # 瑞雯的類 camp = 'Noxus' def __init__(self, nickname, life_value=80, aggresivity=100): self.nickname = nickname self.life_value = life_value self.aggresivity = aggresivity def attack(self, enemy): enemy.life_value -= self.aggresivity # 實例化兩個對象 g1=Garen('草叢猥瑣男') r1=Riven('兔女郎') print(r1.life_value) g1.attack(r1) print(r1.life_value)
二.繼承
繼承指的是類與類之間的關係,是一種什麼是什麼的關係
1 繼承的功能之一:解決類與類之間的代碼重複問題
2 繼承是類與類之間的關係,是一種,什麼是什麼的關係
3 在子類派生出的新的屬性,以自己的爲準
4 在子類派生出的新的方法內重用父類的功能的方式:
指名道姓法 OldboyPeople.__init__ 這種調用方式本身與繼承是沒有關係
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱爲基類或超類,新建的類稱爲派生類或子類
#繼承的基本形式 class ParentClass1: #定義父類 pass class ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類 pass
查看繼承
>>> SubClass1.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
__base__只查看從左到右繼承的第一個子類
__bases__則是查看所有繼承的父類
多繼承的數據查找:
經典類與新式類
1.只有在python2中才分新式類和經典類,python3中統一都是新式類
2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類
3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類
3.在python3中,無論是否繼承object,都默認繼承object,即python3中所有類均爲新式類
繼承原理:(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.如果對下一個類存在兩個合法的選擇,選擇第一個父類
子類中調用父類的方法:
方法一:指名道姓法,即父類名.父類方法()
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' 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): Vehicle.__init__(self,name,speed,load,power) self.line=line def run(self): print('地鐵%s號線歡迎您' %self.line) Vehicle.run(self) line13=Subway('中國地鐵','180m/s','1000人/箱','電',13) line13.run()
方法二:super()
super會根據mro表進行查找,即使沒有直接繼承關係
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()函數時,Python會在MRO列表上繼續搜索下一個類。只要每個重定義的方法統一使用super()並只調用它一次,那麼控制流最終會遍歷完整個MRO列表,每個方法也只會被調用一次(注意注意注意:使用super調用的所有屬性,都是從MRO列表當前的位置往後找,千萬不要通過看代碼去找繼承關係,一定要看MRO列表)
繼承與抽象
先抽象後繼承
抽象就是抽取類似或者比較像的部分,劃分類別,抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
繼承是基於抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構
三.組合
在一個類中以另外一個類的對象作爲數據屬性,稱爲類的組合
組合與繼承都是有效地利用已有類的資源的重要方式
1.繼承的方式
通過繼承建立了派生類與基類之間的關係,它是一種'是'的關係,比如白馬是馬
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如老師是人,學生是人
2.組合的方式
用組合的方式建立了類與組合的類之間的關係,它是一種‘有’的關係,比如教授有生日,教授教python和linux課程,教授有學生s1、s2、s3...
class OldboyPeople: # 定義老男孩人這個基類 school = 'oldboy' def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def eat(self): print('is eating') class OldboyStudent(OldboyPeople): #老男孩學生類,繼承OldboyPeople類 def __init__(self,name,age,sex): OldboyPeople.__init__(self,name,age,sex) self.course=[] def learn(self): print('%s is learning' %self.name) class OldboyTeacher(OldboyPeople): def __init__(self,name,age,sex,salary,title): OldboyPeople.__init__(self,name,age,sex) self.salary=salary self.title=title self.course=[] def teach(self): print('%s is teaching' %self.name) class Course: def __init__(self,course_name,course_period,course_price): self.course_name=course_name self.course_period=course_period self.course_price=course_price def tell_info(self): print('<課程名:%s 週期:%s 價格:%s>' %(self.course_name,self.course_period,self.course_price)) python=Course('Python','6mons',3000) # 實例化課程 linux=Course('Lnux','3mons',2000) bigdata=Course('BigData','1mons',1000) # 實例化老師 egon_obj=OldboyTeacher('egon',18,'male',3.1,'沙河霸道金牌講師') egon_obj.course.append(python) # 爲老師添加課程到課程列表 egon_obj.course.append(linux) for obj in egon_obj.course: # 循環打印egon老師的課程列表 obj.tell_info() yl_obj=OldboyStudent('yanglei',28,'female') # 實例化學生 yl_obj.course.append(python) # 爲學生添加課程到課程列表 for i in yl_obj.course: i.tell_info()
四.綁定方法與非綁定方法
類中定義的函數分爲兩大類:
綁定方法(綁定給誰,誰來調用就自動將它本身當作第一個參數傳入):
1. 綁定到類的方法:用classmethod裝飾器裝飾的方法,爲類量身定製
類.boud_method() # 自動將類當作第一個參數傳入
(其實對象也可調用,但仍將類當作第一個參數傳入)
2. 綁定到對象的方法:沒有被任何裝飾器裝飾的方法,爲對象量身定製
對象.boud_method() # 自動將對象當作第一個參數傳入
(屬於類的函數,類可以調用,但是必須按照函數的規則來,沒有自動傳值那麼一說)
classmehtod是給類用的,即綁定到類,類在使用時會將類本身當做參數傳給類方法的第一個參數(即便是對象來調用也會將類當作第一個參數傳入),python爲我們內置了函數classmethod來把類中的函數定義成類方法
練習:
setting.py文件內容:
HOST='192.168.31.1' PORT=3106
主程序:
import settings class MySql: def __init__(self,host,port): self.host=host self.port=port @classmethod # 把classmethod函數當做裝飾器 def from_conf(cls): # 使用裝飾器後創建函數後會自動傳入cls參數 return cls(settings.HOST,settings.PORT) conn1=MySql('127.0.0.1',3306) # 使用普通方法調用類生成一個對象 conn2=MySql.from_conf() # 調用含有裝飾器的函數來生成一個對象,此函數會把類當做第一個參數(cls)傳入 print(conn1.host,conn2.host)
非綁定方法(用staticmethod裝飾器裝飾的方法)
不與類或對象綁定,類和對象都可以調用,但是沒有自動傳值那麼一說,就是一個普通工具而已
注意:與綁定到對象方法區分開,在類中直接定義的函數,沒有被任何裝飾器裝飾的,都是綁定到對象的方法,可不是普通函數,對象調用該方法會自動傳值,而staticmethod裝飾的方法,不管誰來調用,都沒有自動傳值一說
在類內部用staticmethod裝飾的函數即非綁定方法,就是普通函數
statimethod不與類或對象綁定,誰都可以調用,沒有自動傳值效果
練習:
setting.py文件內容:
HOST='192.168.31.1' PORT=3106
主程序:
import settings import uuid class MySql: def __init__(self,host,port): self.host=host self.port=port self.id=self.create_id() # @classmethod # def from_conf(cls): #綁定給類的 # print(cls) # # return cls(settings.HOST,settings.PORT) # # def func1(self): #綁定給對象的 # pass @staticmethod def create_id(): #非綁定方法,不跟任何綁定 return str(uuid.uuid1()) conn1=MySql('127.0.0.1',3306) conn2=MySql('127.0.0.2',3306) conn3=MySql('127.0.0.3',3306) print(conn1.id,conn2.id,conn3.id)
五、接口與歸一化設計
接口提取了一羣類共同的函數,可以把接口當做一個函數的集合,然後讓子類去實現接口中的函數
歸一化,就是基於同一個接口實現的類,那麼所有的這些類產生的對象在使用時,從用法上來說都一樣
好處:
a. 歸一化讓使用者無需關心對象的類是什麼,只需要知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
b. 歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合
就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然後做出針對性的設計:細緻到什麼程度,視需求而定)
再比如:我們有一個汽車接口,裏面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大衆汽車的類,他們都實現了汽車接口,這樣就好辦了,大家只需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大衆我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣
在python中根本就沒有一個叫做interface的關鍵字,如果要去模仿接口的概念
a.可以藉助第三方模塊:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py裏使用zope.interface
b.使用繼承
繼承的兩種用途
一:繼承基類的方法,並且做出自己的改變或者擴展(代碼重用):實踐中,繼承的這種用途意義並不很大,甚至常常是有害的。因爲它使得子類與基類出現強耦合。
二:聲明某個子類兼容於某基類,定義一個接口類(模仿java的Interface),接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能
class Interface:#定義接口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('進程數據的寫方法') # 實例化三個對象 t=Txt() s=Sata() p=Process() t.read() s.read() p.read() 輸出: 文本數據的讀取方法 硬盤數據的讀取方法 進程數據的讀取方法
六、抽象類
抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化
如果說類是從一堆對象中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。你永遠無法吃到一個叫做水果的東西。
從設計角度去看,如果類是從現實對象抽象而來的,那麼抽象類就是基於類抽象而來的。
從實現角度來看,抽象類與普通類的不同之處在於:抽象類中只能有抽象方法(沒有實現功能),該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與接口有點類似,但其實是不同的,抽象類的本質還是類,指的是一組類的相似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的相似性。
抽象類是一個介於類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現歸一化設計
import abc class Interface(metaclass=abc.ABCMeta):#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關鍵字來定義一個接口。 all_type='file' @abc.abstractmethod def read(self): #定接口函數read pass @abc.abstractmethod def write(self): #定義接口函數write pass class Txt(Interface): #文本,具體實現read和write def read(self): pass def write(self): pass t=Txt() print(t.all_type)
七、多態和多態性
一種接口,多種實現
允許你將父對象設置成爲和一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。
封裝可以隱藏實現細節,使得代碼模塊化;繼承可以擴展已存在的代碼模塊(類);它們的目的都是爲了——代碼重用
而多態則是爲了實現另一個目的——接口重用!多態的作用,就是爲了類在繼承和派生的時候,保證使用“家譜”中任一類的實例的某一屬性時的正確調用
八、封裝
把類內部的變量名或者函數名前面加上__,在類外部就無法調用,在類內部能調用
class Foo: __N=111111 #_Foo__N 在語法檢測階段會自動轉換 def __init__(self,name): self.__Name=name #self._Foo__Name=name def __f1(self): #_Foo__f1 print('f1') def f2(self): self.__f1() #self._Foo__f1() f=Foo('egon') #實例化一個對象 print(f.__N) #無法調用 f.__f1() #無法調用 f.__Name #無法調用 f.f2() #屬於類內部的調用,返回結果f1 如果要訪問使用這種隱藏方法的類的隱藏屬性,可以使用下面的調用方法: print(f. _Foo __N) f._Foo __f1 f._Foo __Name
這種隱藏方法特點:
a.只是一種語法上變形操作,並不會將屬性真正隱藏起來
b. 這種語法級別的變形,是在類定義階段發生的,並且只在類定義階段發生
Foo.__x=123123123123123123123123123123123123123123 #在類外部進行定義變量 print(Foo.__dict__) # 可以檢測到屬性 print(Foo.__x) # 可以調用,說明在此隱藏方法只在定義時發生,類外部無效 f.__x=123123123 print(f.__dict__) print(f.__x)
c. 在子類定義的__x不會覆蓋在父類定義的__x,因爲子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的
class Foo: def __f1(self): #在定義階段變形成了_Foo__f1 print('Foo.f1') def f2(self): self.__f1() #self._Foo_f1 class Bar(Foo): def __f1(self): #在這一步實際變形成了_Bar__f1,不會覆蓋父類中的__f1 print('Bar.f1') # b=Bar() # b.f2()
這種變形需要注意的問題是:
1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了,如a._A__N
2.變形的過程只在類的定義是發生一次,在定義後的賦值操作,不會變形
3.在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義爲私有的
在Python中封裝不是單純意義上的隱藏
a.封裝數據屬性
將數據隱藏起來這不是目的。隱藏起來然後對外提供操作該數據的接口,然後我們可以在接口附加上對該數據操作的限制,以此完成對數據屬性操作的嚴格控制
class People: def __init__(self,name,age): if not isinstance(name,str): raise TypeError('%s must be str' %name) if not isinstance(age,int): raise TypeError('%s must be int' %age) self.__Name=name #隱藏數據屬性 self.__Age=age def tell_info(self): #把隱藏屬性放在函數中,調用時直接調用函數 print('<名字:%s 年齡:%s>' %(self.__Name,self.__Age)) def set_info(self,x,y): if not isinstance(x,str): raise TypeError('%s must be str' %x) if not isinstance(y,int): raise TypeError('%s must be int' %y) self.__Name=x self.__Age=yp=People('egon',18) p.tell_info() #調用查看接口,可以調用隱藏的屬性p.set_info('Egon',19) p.tell_info() #調用修改接口,可以調用隱藏的屬性
b.封裝函數屬性
爲了隔離複雜度
例如:
取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
對使用者來說,只需要知道取款這個功能即可,其餘功能我們都可以隱藏起來,很明顯這麼做
隔離了複雜度,同時也提升了安全性
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() #用戶在取款時只需要調用取款的接口就行了
九、靜態屬性(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 #在調用bmi()函數方法時可以把bmi當做屬性來調用 def bmi(self): return self.weight / (self.height**2) p=People('egon',75,1.80) print(p.bmi) # 在調用時不用加()運行
訪問、設置、刪除
class Foo: def __init__(self,x): self.__Name=x @property def name(self): return self.__Name @name.setter #前提是name已經被property修飾過至少一次,才能調setter def name(self,val): if not isinstance(val,str): raise TypeError self.__Name=val @name.deleter def name(self): # print('=-====>') # del self.__Name raise PermissionError f=Foo('egon') print(f.name) f.name='Egon' #修改操作,調用name.setter修飾過的name函數 print(f.name) del f.name #刪除操作,調用name.deleter修飾的name函數 print(f.name)