py16 面向對象深入
類的繼承
什麽是繼承:
在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‘>)
經典類與新式類
1.只有在python2中才分新式類和經典類,python3中統一都是新式類 2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類 3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類 3.在python3中,無論是否繼承object,都默認繼承object,即python3中所有類均為新式類
提示:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現
>>> ParentClass1.__bases__ (<class ‘object‘>,) >>> ParentClass2.__bases__ (<class ‘object‘>,)
繼承的實現原理
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列表並遵循如下三條準則:
- 子類會先於父類被檢查
- 多個父類會根據它們在列表中的順序被檢查
- 如果對下一個類存在兩個合法的選擇,選擇第一個父類
在Java和C#中子類只能繼承一個父類,而Python中子類可以同時繼承多個父類,如果繼承了多個父類,那麽屬性的查找方式有兩種,分別是:深度優先和廣度優先
示範代碼
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中才分新式類與經典類
在子類中調用父類的方法
在子類派生出的新方法中,往往需要重用父類的方法,我們有兩種方式實現
方式一:指名道姓,即父類名.父類方法() 註意:這種調用需要在參數中加self
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() 註意:這種調用不用在參數中加self
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()是依賴於繼承的,並且即使沒有直接繼承關系,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‘>]
組合與繼承
組合就是把一個對象傳給另一個類作為類的參數,或者把對象添加到另一個對象中
>>> 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() #可以使用組合的類產生的對象所持有的方法 release Fire skill class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price def tell_info(self): print(‘<%s %s %s>‘ %(self.name,self.period,self.price)) class Teacher(People): def __init__(self,name,age,sex,job_title): People.__init__(self,name,age,sex) self.job_title=job_title self.course=[] self.students=[] class Student(People): def __init__(self,name,age,sex): People.__init__(self,name,age,sex) self.course=[] egon=Teacher(‘egon‘,18,‘male‘,‘沙河霸道金牌講師‘) s1=Student(‘牛榴彈‘,18,‘female‘) python=Course(‘python‘,‘3mons‘,3000.0) linux=Course(‘python‘,‘3mons‘,3000.0) #為老師egon和學生s1添加課程 egon.course.append(python) egon.course.append(linux) s1.course.append(python) #為老師egon添加學生s1 egon.students.append(s1) #使用 for obj in egon.course: obj.tell_info()
抽象類和接口
首先看java為什麽有接口,因為java的類不能多繼承,接口用來實現多繼承,且java接口中方法都為抽象方法。
java中抽象類和接口的區別:抽象類由abstract關鍵字來修飾,接口由interface關鍵字來修飾。抽象類中除了有抽象方法外,也可以有數據成員和非抽象方法;而接口中所有的方法必須都是抽象的,接口中也可以定義數據成員,但必須是常量。
接口
在python中根本就沒有一個叫做interface的關鍵字,如果非要去模仿接口的概念
可以借助第三方模塊:http://pypi.python.org/pypi/zope.interface
在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)
封裝和類中各種特殊方法
封裝
在python中用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的)
【非特殊調用情況下,子類和實例化的對象不能繼承,重寫,使用該方法或屬性。特殊調用都可以,原因在下面代碼裏】
#其實這僅僅這是一種變形操作,在解釋器解析class時自動變形 #類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式: #由於只有在解析class定義時才會變形,所以只要是本類外的類或對象都無法訪問原形式 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只能在內部使用,如self.__x,引用的就是變形的結果。
- 這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。
- 在子類定義的__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
封裝的意義
1:封裝數據,將數據隱藏起來這不是目的。隱藏起來然後對外提供操作該數據的接口才是目的
class Teacher: def __init__(self,name,age): self.__name=name self.__age=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()
各種特殊方法:classmethod,staticmethod,property
staticmethod
在類的方法定義語句上方寫上@staticmethod代表其為靜態方法,該方法與類的聯系不大,靜態方法沒有默認的self參數,和普通函數一樣,當然也可以給他傳self(對象名)
classmethod
把類中的函數定義成類方法,使self.參數無法訪問對象的參數,只能訪問類裏的同名參數
class Role: name = "我是類name" def __init__(self, name, role, weapon): self.name = name self.role = role self.weapon = weapon @staticmethod def stamethod(a,self): print(self.name) print(a) @classmethod def clsmethod(cls): # def clsmethod(self): print(cls.name) # print(self.name) r1 = Role(‘Chenronghua‘, ‘police‘, ‘AK47‘) r1.clsmethod() r1.stamethod(‘沒有默認參數,self需要自己傳‘, r1) ############### 我是類name Chenronghua 沒有默認參數,self需要自己傳
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 # 當對name賦值時該函數被調用 def name(self,value): if not isinstance(value,str): #在設定值之前進行類型檢查 raise TypeError(‘%s must be str‘ %value) self.__NAME=value #通過類型檢查後,將值value存放到真實的位置self.__NAME @name.deleter # 當name被刪除時,該函數被調用 def name(self): raise TypeError(‘Can not delete‘) f=Foo(‘egon‘) print(f.name) # f.name=10 #賦值,調用setter,拋出異常‘TypeError: 10 must be str‘ del f.name #刪除,調用deleterious,拋出異常‘TypeError: Can not delete‘
元類及元類控制類的創建,實例化過程
參考http://www.cnblogs.com/linhaifeng/articles/8029564.html
一、知識儲備
exec:三個參數
參數一:字符串形式的命令 參數二:全局作用域(字典形式),如果不指定,默認為globals(),把運行中全局變量傳入globals 參數三:局部作用域(字典形式),如果不指定,默認為locals(),把運行中局部變量傳入locals
exec的使用
#可以把exec命令的執行當成是一個函數的執行,會將執行期間產生的名字存放於局部名稱空間中 g={ ‘x‘:1, ‘y‘:2 } l={} exec(‘‘‘ global x,z x=100 z=200 m=300 ‘‘‘,g,l) print(g) #{‘x‘: 100, ‘y‘: 2,‘z‘:200,......} print(l) #{‘m‘: 300}
二 、什麽是元類?
首先要知道python中萬物皆是對象,類也是對象,元類是類的類,是類的模板
元類是用來控制如何創建類的,正如類是創建對象的模板一樣,而元類的主要目的是為了控制類的創建行為
元類的實例化的結果為我們用class定義的類,正如類的實例為對象(f1對象是Foo類的一個實例,Foo類是 type 類的一個實例)
type是python的一個內建元類,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象
class Foo: pass f1=Foo() #f1是通過Foo類實例化的對象 #type函數可以查看類型,也可以用來查看對象的類,二者是一樣的 print(type(f1)) # 輸出:<class ‘__main__.Foo‘> 表示,obj 對象由Foo類創建 print(type(Foo)) # 輸出:<class ‘type‘>
三、創建類的兩種方式
第一步先看類的創建流程:
首先,要知道,解釋器解釋到新變量時會分配名稱空間,類創建也不例外,類體定義的名字都會存放於類的名稱空間中(一個局部的名稱空間),然後對名稱空間進行填充。
第二步調用元類來創建類。
方式一:使用class關鍵字
class Chinese(object): country=‘China‘ def __init__(self,name,age): self.name=name self.age=age def talk(self): print(‘%s is talking‘ %self.name)
方式二:就是手動模擬class創建類的過程):將創建類的步驟拆分開,手動去創建
#準備工作: #創建類主要分為三部分 1 類名 2 類的父類(元類) 3 類體 #類名 class_name=‘Chinese‘ #類的父類 class_bases=(object,) #類體 class_body=""" country=‘China‘ def __init__(self,name,age): self.name=name self.age=age def talk(self): print(‘%s is talking‘ %self.name) """
步驟一(先處理類體->名稱空間):類體定義的名字都會存放於類的名稱空間中(一個局部的名稱空間),我們可以事先定義一個空字典,然後用exec去執行類體的代碼(exec產生名稱空間的過程與真正的class過程類似,只是後者會將__開頭的屬性變形),生成類的局部名稱空間,即填充字典
class_dic={} exec(class_body,globals(),class_dic) print(class_dic) #{‘country‘: ‘China‘, ‘talk‘: <function talk at 0x101a560c8>, ‘__init__‘: <function __init__ at 0x101a56668>}
步驟二:調用元類type(也可以自定義)來產生類Chinense
Foo=type(class_name,class_bases,class_dic) #實例化type得到對象Foo,即我們用class定義的類Foo print(Foo, Foo.country) print(type(Foo)) print(isinstance(Foo,type)) ‘‘‘ <class ‘__main__.Chinese‘> China#表示由Chinese類創建,這也是type的第一個參數的作用 <class ‘type‘> True ‘‘‘
我們看到,type 接收三個參數:
- 第 1 個參數是字符串‘Chinese’,表示類名,print(Foo)時即可看到,第一個參數和type等號左邊的參數可保持一致
- 第 2 個參數是元組 (object, ),表示所有的父類
- 第 3 個參數是字典,這裏是一個空字典,表示沒有定義屬性和方法
補充:若Foo類有繼承,即class Foo(Bar):.... 則等同於type(‘Foo‘,(Bar,),{})
元類控制類的行為(類創建及實例化總流程)
知識儲備:
類默認繼承object,新式類不寫也默認繼承。
__call__方法在對象被調用時被使用,類也是對象,所以會調用父類(元類)的__call__
類實例化對象實際上經歷三件事:1、產生空對象obj 2、初始化 3、返回obj
代碼如下,講解在代碼下方
class Mymeta(type): #繼承默認元類的一堆屬性 def __init__(self,class_name,class_bases,class_dic): if not class_name.istitle(): raise TypeError(‘類名首字母必須大寫‘) super(Mymeta,self).__init__(class_name,class_bases,class_dic) def __call__(self, *args, **kwargs): #self=People print(self,args,kwargs) #<class ‘__main__.People‘> (‘egon‘, 18) {} #1、調用self,即People下的函數__new__,在該函數內完成:1、產生空對象obj 2、初始化 3、返回obj obj=self.__new__(self,*args,**kwargs) #2、一定記得返回obj,因為實例化People(...)取得就是__call__的返回值 return obj class People(object,metaclass=Mymeta): country=‘China‘ def __init__(self,name,age): self.name=name self.age=age def talk(self): print(‘%s is talking‘ %self.name) def __new__(cls, *args, **kwargs): obj=object.__new__(cls) cls.__init__(obj,*args,**kwargs) return obj obj=People(‘egon‘,18) print(obj.__dict__) #{‘name‘: ‘egon‘, ‘age‘: 18} #####調試順序,按行號來##### #1-2-18-19-21-25-2-3-6-33-10-13-29-30-21-22-23-31-16-34
難以理解的幾個點:
1.為什麽從25跳到2,因為此時People還未被調用,還沒輪到__call__方法出馬,此時參考第二種type創建類的方式,看看__init__的參數是不是和type參數一樣?這裏就是在普通類被實例化之前先調用初始化函數,初始化完成立馬到33進行普通類(普通類也是對象)的調用,然後進入__call__方法。
2.進入call方法之後的13-29及以後:在33行普通類被調用後進入8行的call方法後,需要完成三個指標,1、產生空對象obj 2、初始化 3、返回obj。在元類的__call__方法裏調用了普通類的__new__方法進行三步走,普通類使用object.__new__(cls)來創建空對象,然後對空對象進行初始化(30-21),最後返回對象,大功告成。
元類應用
#應用:定制元類實現單例模式 class Mymeta(type): def __init__(self,name,bases,dic): #定義類Mysql時就觸發 self.__instance=None super().__init__(name,bases,dic) def __call__(self, *args, **kwargs): #Mysql(...)時觸發 if not self.__instance: self.__instance=object.__new__(self) #產生對象 self.__init__(self.__instance,*args,**kwargs) #初始化對象 #上述兩步可以合成下面一步 # self.__instance=super().__call__(*args,**kwargs) return self.__instance class Mysql(metaclass=Mymeta): def __init__(self,host=‘127.0.0.1‘,port=‘3306‘): self.host=host self.port=port obj1=Mysql() obj2=Mysql() print(obj1 is obj2)
py16 面向對象深入