Python-面向对象进阶
一、isinstance(obj, cls) and issubclass(sub, super)
1. isinstance(obj, cls),檢查obj是否是類cls的對(duì)象
1 class A: 2 pass 3 4 obj = A() 5 print(isinstance(obj, A)) 6 7 #運(yùn)行結(jié)果 8 #True2. issubclass(sub, super),檢查sub類是否是super類的派生類(子類)
1 class A: 2 pass 3 4 class B(A): 5 pass 6 7 print(issubclass(B, A)) 8 9 #運(yùn)行結(jié)果 10 #True二、反射
1. 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問(wèn)、檢測(cè)和修改它本身狀態(tài)或行為的一種能力(自省)。這一概念的提出很快引發(fā)了計(jì)算機(jī)科學(xué)領(lǐng)域關(guān)于應(yīng)用反射性的研究。它首先被程序語(yǔ)言的設(shè)計(jì)領(lǐng)域所采用,并在Lisp和面向?qū)ο蠓矫嫒〉昧顺煽?jī)。
2. python面向?qū)ο笾械姆瓷?#xff1a;通過(guò)字符串的形式操作對(duì)象相關(guān)的屬性。python中的一切事物都是對(duì)象(都可以使用反射)。
1 class People: 2 country = 'China' 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 def info(self): 8 print('%s is %d years old' % (self.name, self.age)) 9 10 p = People('jack', 18) 11 12 #hasattr(obj, name),檢查屬性 13 print(hasattr(p, 'info')) #檢查對(duì)象p是否有‘info’屬性,結(jié)果True 14 print(hasattr(p, 'name')) #檢查對(duì)象p是否有‘name’屬性,結(jié)果True 15 16 #getattr(obj, name)獲取屬性 17 print(getattr(p, 'name')) #獲得對(duì)象p的‘name’屬性,結(jié)果:返回p.name的值,即jack 18 print(getattr(p, 'info')) #獲得對(duì)象p的‘info’屬性,結(jié)果:返回p.info的值, 19 # 即綁定方法info的內(nèi)存地址:<bound method People.info of <__main__.People object at 0x000001B64317ABE0>> 20 getattr(p, 'info')() #由于getattr返回的是對(duì)象方法屬性的內(nèi)存地址,加()就可以調(diào)用,結(jié)果:jack is 18 years old 21 22 #setattr(x, y, v)設(shè)置屬性 23 setattr(p, 'age', 21) #修改對(duì)象p的‘a(chǎn)ge’屬性,結(jié)果:p.age的值變?yōu)?1 24 setattr(p, 'sex', 'male') #新增對(duì)象p的‘sex’屬性,結(jié)果:p.sex的值為male 25 print(p.__dict__) #查看對(duì)象p的數(shù)據(jù)屬性,結(jié)果:{'name': 'jack', 'age': 21, 'sex': 'male'} 26 27 #delattr(x, y)刪除屬性 28 delattr(p, 'sex') #刪除對(duì)象p的‘sex’屬性 29 print(p.__dict__) #結(jié)果:{'name': 'jack', 'age': 21} 四個(gè)可以實(shí)現(xiàn)自省的函數(shù):hasattr(obj, name);getattr(obj, name);setattr(x, y, v);delattr(x, y) 1 #類也是對(duì)象 2 class Foo(object): 3 staticField = "old boy" 4 5 def __init__(self): 6 self.name = 'wupeiqi' 7 8 def func(self): 9 return 'func' 10 11 @staticmethod 12 def bar(): 13 return 'bar' 14 15 16 print(getattr(Foo, 'staticField')) #獲取類的'staticField'屬性,結(jié)果:old boy 17 18 print(getattr(Foo, 'func')) #獲取類的'func'屬性,結(jié)果:<function Foo.func at 0x0000018156FBB950> 19 print(getattr(Foo, 'func')('self')) #加()調(diào)用方法,結(jié)果:func 20 21 print(getattr(Foo, 'bar')) #獲取類的'bar'屬性,結(jié)果:<function Foo.bar at 0x00000192D2AFB9D8> 22 print(getattr(Foo, 'bar')()) #加()調(diào)用方法,結(jié)果:bar 類也是對(duì)象,能夠應(yīng)用反射 1 #反射當(dāng)前模塊成員 2 #!/usr/bin/env python 3 # -*- coding:utf-8 -*- 4 5 import sys 6 7 8 def s1(): 9 print('s1') 10 11 12 def s2(): 13 print('s2') 14 15 16 this_module = sys.modules[__name__] 17 18 print(this_module) #結(jié)果:<module '__main__' from '......'> 19 print(hasattr(this_module, 's1')) #結(jié)果:True 20 print(getattr(this_module, 's2')) #結(jié)果:<function s2 at 0x0000020590EAB8C8> 21 getattr(this_module, 's2')() #結(jié)果:s2 模塊也是對(duì)象,能夠應(yīng)用反射3. 反射的好處
好處一:實(shí)現(xiàn)可插拔機(jī)制
有倆程序員,一個(gè)lili,一個(gè)是egon,lili在寫程序的時(shí)候需要用到egon所寫的類,但是egon去跟女朋友度蜜月去了,還沒(méi)有完成他寫的類,lili想到了反射,使用了反射機(jī)制lili可以繼續(xù)完成自己的代碼,等egon度蜜月回來(lái)后再繼續(xù)完成類的定義并且去實(shí)現(xiàn)lili想要的功能。
總之反射的好處就是,可以事先定義好接口,接口只有在被完成后才會(huì)真正執(zhí)行,這實(shí)現(xiàn)了即插即用,這其實(shí)是一種‘后期綁定’,什么意思?即你可以事先把主要的邏輯寫好(只定義接口),然后后期再去實(shí)現(xiàn)接口的功能。
1 class FtpClient: 2 'ftp客戶端,但是還么有實(shí)現(xiàn)具體的功能' 3 def __init__(self,addr): 4 print('正在連接服務(wù)器[%s]' %addr) 5 self.addr=addr 6 7 ############################## 8 #不影響lili的代碼編寫 9 10 from module import FtpClient 11 f1=FtpClient('192.168.1.1') 12 if hasattr(f1,'get'): 13 func_get=getattr(f1,'get') 14 func_get() 15 else: 16 print('---->不存在此方法') 17 print('處理其他的邏輯')好處二:動(dòng)態(tài)導(dǎo)入模塊(基于反射當(dāng)前模塊成員)
1 #兩種導(dǎo)入用戶輸入模塊得方法,官方推薦方法2 2 #方法1 3 m = input('input your module:') #用戶輸入要導(dǎo)入的模塊名,以time模塊為例 4 m1 = __import__(m) 5 print(m1) #結(jié)果:<module 'time' (built-in)> 6 print(m1.time()) #結(jié)果:1493023753.0157707,當(dāng)前時(shí)間 7 8 #方法2 9 import importlib #先導(dǎo)入importlib模塊 10 t = importlib.import_module(m) 11 print(t) #結(jié)果:<module 'time' (built-in)> 12 print(t.time()) #結(jié)果:1493023753.0238242,當(dāng)前時(shí)間三、內(nèi)置attr
1 class Foo: 2 x = 1 3 def __init__(self, y): 4 self.y = y 5 6 def __getattr__(self, item): 7 print('----> from getattr:你找的屬性不存在') 8 9 def __setattr__(self, key, value): 10 print('----> from setattr') 11 # self.key=value #這就無(wú)限遞歸了 12 self.__dict__[key] = value #應(yīng)該使用它 13 14 def __delattr__(self, item): 15 print('----> from delattr') 16 # del self.item #無(wú)限遞歸了 17 self.__dict__.pop(item) #應(yīng)該使用它 18 19 #__setattr__添加/修改屬性會(huì)觸發(fā)它的執(zhí)行 20 f1 = Foo(10) #因?yàn)橹貙懥薩_setattr__,凡是賦值操作都會(huì)觸發(fā)它的運(yùn)行 21 print(f1.__dict__) #結(jié)果:----> from setattr {'y': 10} 22 f1.z = 3 #添加屬性 23 print(f1.__dict__) #結(jié)果:----> from setattr {'y': 10, 'z': 3} 24 25 #__delattr__刪除屬性的時(shí)候會(huì)觸發(fā) 26 f1.__dict__['a'] = 3 #我們可以直接修改屬性字典,來(lái)完成添加/修改屬性的操作 27 del f1.a #觸發(fā)__delattr__ 28 print(f1.__dict__) #結(jié)果:----> from delattr {'y': 10, 'z': 3} 29 30 #__getattr__只有在使用對(duì)象調(diào)用屬性且屬性不存在的時(shí)候才會(huì)觸發(fā) 31 print(f1.y) #屬性存在,結(jié)果:10 32 f1.a #屬性a不存在,觸發(fā)__getattr__,結(jié)果:----> from getattr:你找的屬性不存在四、二次加工標(biāo)準(zhǔn)類型(包裝)
包裝:python為大家提供了標(biāo)準(zhǔn)數(shù)據(jù)類型,以及豐富的內(nèi)置方法,其實(shí)在很多場(chǎng)景下我們都需要基于標(biāo)準(zhǔn)數(shù)據(jù)類型來(lái)定制我們自己的數(shù)據(jù)類型,新增/改寫方法,這就用到了我們剛學(xué)的繼承/派生知識(shí)(其他的標(biāo)準(zhǔn)類型均可以通過(guò)下面的方式進(jìn)行二次加工)
?
1 #二次加工標(biāo)準(zhǔn)類型(基于繼承實(shí)現(xiàn)) 2 class List(list): #繼承l(wèi)ist所有的屬性,也可以派生出自己新的,比如append和mid 3 def append(self, p_object): 4 ' 派生自己的append:加上類型檢查' 5 if not isinstance(p_object, int): 6 raise TypeError('must be int') 7 super().append(p_object) 8 9 @property 10 def mid(self): 11 '新增自己的屬性' 12 index = len(self)//2 13 return self[index] 14 15 l = List([1, 2, 3, 4]) 16 print(l) 17 l.append(5) 18 print(l) #結(jié)果:[1, 2, 3, 4, 5] 19 # l.append('1111111') #報(bào)錯(cuò),必須為int類型 20 21 print(l.mid) #結(jié)果:3 22 23 #其余的方法都繼承l(wèi)ist的 24 l.insert(0, -123) #插入元素 25 print(l) #結(jié)果:[-123, 1, 2, 3, 4, 5] 26 l.clear() #清空列表 27 print(l) #結(jié)果:[]授權(quán):授權(quán)是包裝的一個(gè)特性,?包裝一個(gè)類型通常是對(duì)已存在的類型的一些定制,這種做法可以新建,修改或刪除原有產(chǎn)品的功能。其它的則保持原樣。授權(quán)的過(guò)程,即是所有更新的功能都是由新類的某部分來(lái)處理,但已存在的功能就授權(quán)給對(duì)象的默認(rèn)屬性。
實(shí)現(xiàn)授權(quán)的關(guān)鍵點(diǎn)就是覆蓋__getattr__方法
1 # 授權(quán)示范 2 import time 3 4 5 class FileHandle: 6 def __init__(self, filename, mode='r', encoding='utf-8'): 7 self.file = open(filename, mode, encoding=encoding) #獲得文件句柄 8 9 def write(self, line): #重新定義write方法,新增添加時(shí)間的功能 10 t = time.strftime('%Y-%m-%d %T') 11 self.file.write('%s %s' % (t, line)) 12 13 def __getattr__(self, item): #文件操作的其它屬性在FileHandle類中找不到時(shí),觸發(fā)__getattr__ 14 return getattr(self.file, item) 15 16 17 f1 = FileHandle('b.txt', 'w+') #新建文件b.txt,獲得文件句柄,賦給對(duì)象f1 18 f1.write('你好啊') #調(diào)用類中的定制方法write 19 f1.seek(0) #重置文件位置于文首,觸發(fā)__getattr__,正常調(diào)用 20 print(f1.read()) #打印文件內(nèi)容,觸發(fā)__getattr__,正常調(diào)用,結(jié)果:2017-04-24 17:30:37 你好啊 21 f1.close() #關(guān)閉文件,觸發(fā)__getattr__,正常調(diào)用五、__getattribute__
__getattribute__是訪問(wèn)屬性的方法,我們可以通過(guò)方法重寫來(lái)擴(kuò)展方法的功能。
當(dāng)獲取屬性時(shí),直接return object.__getattribute__(self, *args, **kwargs)
如果需要獲取某個(gè)方法的返回值時(shí),則需要在函數(shù)后面加上一個(gè)()即可。如果不加的話,返回的是函數(shù)引用地址。
1 class Foo: 2 def __init__(self, x): 3 self.x = x 4 self.y = 100 5 6 def __getattr__(self, item): #屬性不存在時(shí)觸發(fā)執(zhí)行 7 print('getattr') 8 if item == 'y': 9 return 'y = 100' 10 else: 11 return "No %s attribute" %item 12 13 def __getattribute__(self, item): #屬性存不存在都會(huì)觸發(fā)執(zhí)行,而且當(dāng)與__getattr__同時(shí)存在時(shí),僅執(zhí)行自己,除非拋出錯(cuò)誤后,會(huì)執(zhí)行__getattr__ 14 print('__getattribute__ is called') 15 if item == 'x': 16 return 'x = %s' %(object.__getattribute__(self, item)) #返回屬性 17 else: 18 raise AttributeError("No 'x' attribute") #當(dāng)拋出錯(cuò)誤時(shí),會(huì)去執(zhí)行__getattr__ 19 20 21 f = Foo(10) 22 print(f.x) #object存在‘x’屬性,運(yùn)行結(jié)果:__getattribute__ is called x = 10 23 print(f.y) #object存在‘y’屬性,觸發(fā)__getattribute__,不符合if條件,拋出錯(cuò)誤,會(huì)去執(zhí)行__getattr__ 24 #運(yùn)行結(jié)果:__getattribute__ is called,getattr,y = 100 25 print(f.z) #object不存在‘z’屬性,但還會(huì)觸發(fā)_getattribute__,拋出錯(cuò)誤,觸發(fā)__getattr__,返回return的值 26 #運(yùn)行結(jié)果:__getattribute__ is called,getattr,No z attribute六、__setitem__,__getitem,__delitem__
觸發(fā)機(jī)制與attr一致,只是將對(duì)象操作屬性模擬為字典的格式:
1 # 把對(duì)象操作屬性模擬成字典的格式 2 class Foo: 3 def __init__(self, name): 4 self.name = name 5 6 def __getitem__(self, item): 7 return self.__dict__[item] 8 9 def __setitem__(self, key, value): 10 self.__dict__[key] = value 11 12 def __delitem__(self, key): 13 self.__dict__.pop(key) 14 15 16 f = Foo('egon') #實(shí)例化 17 18 print(f.name) #.的方式調(diào)用,運(yùn)行結(jié)果:egon 19 print(f['name']) #字典方式調(diào)用,運(yùn)行結(jié)果:egon 20 21 f.age1 = 18 22 f['age'] = 21 23 print(f.__dict__) #運(yùn)行結(jié)果:{'name': 'egon', 'age1': 18, 'age': 21} 24 25 del f['age1'] 26 del f.age 27 print(f.__dict__) #運(yùn)行結(jié)果:{'name': 'egon'} 把對(duì)象操作屬性模擬成字典的格式七、__str__,__repr__,__format__
改變對(duì)象的字符串顯示__str__,__repr__;自定制格式化字符串__format__
1 # _*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 format_dict = { 4 'nat': '{obj.name}-{obj.addr}-{obj.type}', # 學(xué)校名-學(xué)校地址-學(xué)校類型 5 'tna': '{obj.type}:{obj.name}:{obj.addr}', # 學(xué)校類型:學(xué)校名:學(xué)校地址 6 'tan': '{obj.type}/{obj.addr}/{obj.name}', # 學(xué)校類型/學(xué)校地址/學(xué)校名 7 } 8 9 10 class School: 11 def __init__(self, name, addr, type): 12 self.name = name 13 self.addr = addr 14 self.type = type 15 16 def __repr__(self): 17 return 'School(%s,%s)' % (self.name, self.addr) 18 19 def __str__(self): 20 return '(%s,%s)' % (self.name, self.addr) 21 22 def __format__(self, format_spec): 23 if not format_spec or format_spec not in format_dict: 24 format_spec = 'nat' 25 fmt = format_dict[format_spec] 26 return fmt.format(obj=self) 27 28 29 s1 = School('oldboy1', '北京', '私立') 30 print('from repr: ', repr(s1)) #運(yùn)行結(jié)果:from repr: School(oldboy1,北京) 31 print('from str: ', str(s1)) #運(yùn)行結(jié)果:from str: (oldboy1,北京) 32 print(s1) #默認(rèn)以‘str’定義的方式輸出;運(yùn)行結(jié)果:(oldboy1,北京) 33 34 ''' 35 str函數(shù)或者print函數(shù)--->obj.__str__() 36 repr或者交互式解釋器--->obj.__repr__() 37 如果__str__沒(méi)有被定義,那么就會(huì)使用__repr__來(lái)代替輸出 38 注意:這倆方法的返回值必須是字符串,否則拋出異常 39 ''' 40 print(format(s1, 'nat')) #指定格式‘nat’輸出;運(yùn)行結(jié)果:oldboy1-北京-私立 41 print(format(s1, 'tna')) #指定格式‘tna’輸出;運(yùn)行結(jié)果:私立:oldboy1:北京 42 print(format(s1, 'tan')) #指定格式‘tan’輸出;運(yùn)行結(jié)果:私立/北京/oldboy1 43 print(format(s1, 'asfdasdffd')) #其它情況默認(rèn)以‘nat’輸出;運(yùn)行結(jié)果:oldboy1-北京-私立 自定義格式化輸出實(shí)例八、__slots__
正常情況下,當(dāng)我們定義了一個(gè)class,創(chuàng)建了一個(gè)class的實(shí)例后,我們可以給該實(shí)例綁定任何屬性和方法,這就是動(dòng)態(tài)語(yǔ)言的靈活性。
但是,如果我們想要限制實(shí)例的屬性怎么辦?比如,只允許對(duì)Student實(shí)例添加name和age屬性。為了達(dá)到限制的目的,Python允許在定義class的時(shí)候,定義一個(gè)特殊的__slots__變量,來(lái)限制該class實(shí)例能添加的屬性:
1 ''' 2 1.__slots__是什么:是一個(gè)類變量,變量值可以是列表,元祖,或者可迭代對(duì)象,也可以是一個(gè)字符串(意味著所有實(shí)例只有一個(gè)數(shù)據(jù)屬性) 3 2.引子:使用點(diǎn)來(lái)訪問(wèn)屬性本質(zhì)就是在訪問(wèn)類或者對(duì)象的__dict__屬性字典(類的字典是共享的,而每個(gè)實(shí)例的是獨(dú)立的) 4 3.為何使用__slots__:字典會(huì)占用大量?jī)?nèi)存,如果你有一個(gè)屬性很少的類,但是有很多實(shí)例,為了節(jié)省內(nèi)存可以使用__slots__取代實(shí)例的__dict__ 5 當(dāng)你定義__slots__后,__slots__就會(huì)為實(shí)例使用一種更加緊湊的內(nèi)部表示。實(shí)例通過(guò)一個(gè)很小的固定大小的數(shù)組來(lái)構(gòu)建,而不是為每個(gè)實(shí)例定義一個(gè) 6 字典,這跟元組或列表很類似。在__slots__中列出的屬性名在內(nèi)部被映射到這個(gè)數(shù)組的指定小標(biāo)上。使用__slots__一個(gè)不好的地方就是我們不能再給 7 實(shí)例添加新的屬性了,只能使用在__slots__中定義的那些屬性名。 8 4.注意事項(xiàng):__slots__的很多特性都依賴于普通的基于字典的實(shí)現(xiàn)。另外,定義了__slots__后的類不再 支持一些普通類特性了,比如多繼承。大多數(shù)情況下,你應(yīng)該 9 只在那些經(jīng)常被使用到 的用作數(shù)據(jù)結(jié)構(gòu)的類上定義__slots__比如在程序中需要?jiǎng)?chuàng)建某個(gè)類的幾百萬(wàn)個(gè)實(shí)例對(duì)象 。 10 關(guān)于__slots__的一個(gè)常見(jiàn)誤區(qū)是它可以作為一個(gè)封裝工具來(lái)防止用戶給實(shí)例增加新的屬性。盡管使用__slots__可以達(dá)到這樣的目的,但是這個(gè)并不是它的初衷。 更多的是用來(lái)作為一個(gè)內(nèi)存優(yōu)化工具。 11 12 ''' 13 14 class Student(object): 15 __slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱 16 17 s = Student() # 創(chuàng)建新的實(shí)例 18 s.name = 'jack' # 綁定屬性'name' 19 s.age = 21 # 綁定屬性'age' 20 #s.score = 99 # 綁定屬性'score';報(bào)錯(cuò):'Student' object has no attribute 'score' 21 22 #使用__slots__要注意,__slots__定義的屬性僅對(duì)當(dāng)前類實(shí)例起作用,對(duì)繼承的子類是不起作用的: 23 class G(Student): 24 pass 25 26 g = G() # 創(chuàng)建新的實(shí)例 27 g.name = 'jack' # 綁定屬性'name' 28 g.age = 21 # 綁定屬性'age' 29 g.score = 99 # 綁定屬性'score' 限制實(shí)例的綁定屬性,當(dāng)前類有效九、__next__和__iter__實(shí)現(xiàn)迭代器協(xié)議
如果一個(gè)類想被用于for ... in循環(huán),類似list或tuple那樣,就必須實(shí)現(xiàn)一個(gè)__iter__()方法,該方法返回一個(gè)迭代對(duì)象,然后,Python的for循環(huán)就會(huì)不斷調(diào)用該迭代對(duì)象的__next__()方法拿到循環(huán)的下一個(gè)值,直到遇到StopIteration錯(cuò)誤時(shí)退出循環(huán)。
我們以斐波那契數(shù)列為例,寫一個(gè)Fib類,可以作用于for循環(huán):
1 class Fib(object): 2 def __init__(self): 3 self.a, self.b = 0, 1 # 初始化兩個(gè)計(jì)數(shù)器a,b 4 5 def __iter__(self): 6 return self # 實(shí)例本身就是迭代對(duì)象,故返回自己 7 8 def __next__(self): 9 self.a, self.b = self.b, self.a + self.b # 計(jì)算下一個(gè)值 10 if self.a > 100: # 退出循環(huán)的條件 11 raise StopIteration() 12 return self.a # 返回下一個(gè)值 13 14 for i in Fib(): 15 print(i) 斐波那契數(shù)列十、__doc__
返回對(duì)象的描述信息
class Foo:"""描述信息"""passprint(Foo.__doc__) #運(yùn)行結(jié)果:描述信息#__doc__不能被繼承 class Bar(Foo):passprint(Bar.__doc__) #運(yùn)行結(jié)果:None十一、__module__和__class__
__module__ 表示當(dāng)前操作的對(duì)象在哪個(gè)模塊;__class__?表示當(dāng)前操作的對(duì)象的類是什么
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 #將該文件存為 a.py 4 5 class C: 6 7 def __init__(self): 8 self.name = 'a.py' #與a.py同一目錄下創(chuàng)建新文件from a import C #從a模塊導(dǎo)入類C obj = C() print(obj.__module__) # 輸出 a,即:輸出模塊 print(obj.__class__) # 輸出 <class 'a.C'>,即:輸出類十二、__del__
析構(gòu)方法,當(dāng)對(duì)象在內(nèi)存中被釋放時(shí),自動(dòng)觸發(fā)執(zhí)行。
注:此方法一般無(wú)須定義,因?yàn)镻ython是一門高級(jí)語(yǔ)言,程序員在使用時(shí)無(wú)需關(guān)心內(nèi)存的分配和釋放,因?yàn)榇斯ぷ鞫际墙唤oPython解釋器來(lái)執(zhí)行,所以,析構(gòu)函數(shù)的調(diào)用是由解釋器在進(jìn)行垃圾回收時(shí)自動(dòng)觸發(fā)執(zhí)行的。
class Foo:def __del__(self):print('執(zhí)行我啦')f1=Foo() del f1 #觸發(fā)__del__ print('------->')#輸出結(jié)果 執(zhí)行我啦 ------->#########################class Foo:def __del__(self):print('執(zhí)行我啦')f1=Foo() # del f1 print('------->')#輸出結(jié)果 -------> 執(zhí)行我啦 #對(duì)于當(dāng)前程序,由于print('------->')運(yùn)行完后程序就結(jié)束了,在結(jié)束前,會(huì)自動(dòng)觸發(fā)__del__十三、__enter__和__exit__
我們知道在操作文件對(duì)象的時(shí)候可以這么寫
1 with open('a.txt') as f: 2 '代碼塊'上述叫做上下文管理協(xié)議,即with語(yǔ)句,為了讓一個(gè)對(duì)象兼容with語(yǔ)句,必須在這個(gè)對(duì)象的類中聲明__enter__和__exit__方法
1 class Open: 2 def __init__(self, name): 3 self.name = name 4 5 def __enter__(self): 6 print('出現(xiàn)with語(yǔ)句,對(duì)象的__enter__被觸發(fā),有返回值則賦值給as聲明的變量') 7 # return self 8 9 def __exit__(self, exc_type, exc_val, exc_tb): 10 print('with中代碼塊執(zhí)行完畢后執(zhí)行__exit__') 11 12 13 with Open('a.txt') as f: 14 print('=====>執(zhí)行代碼塊') 15 16 17 #運(yùn)行結(jié)果: 18 """ 19 出現(xiàn)with語(yǔ)句,對(duì)象的__enter__被觸發(fā),有返回值則賦值給as聲明的變量 20 =====>執(zhí)行代碼塊 21 with中代碼塊執(zhí)行完畢后執(zhí)行__exit__ 22 """ 上下文管理協(xié)議__exit__()中的三個(gè)參數(shù)分別代表異常類型,異常值和追溯信息,with語(yǔ)句中代碼塊出現(xiàn)異常,則with后的代碼都無(wú)法執(zhí)行
1 class Open: 2 def __init__(self, name): 3 self.name = name 4 5 def __enter__(self): 6 print('出現(xiàn)with語(yǔ)句,對(duì)象的__enter__被觸發(fā),有返回值則賦值給as聲明的變量') 7 # return self 8 9 def __exit__(self, exc_type, exc_val, exc_tb): 10 print('with中代碼塊執(zhí)行完畢后執(zhí)行__exit__') 11 print(exc_type) 12 print(exc_val) 13 print(exc_tb) 14 #return True 15 16 with Open('a.txt') as f: 17 print('=====>執(zhí)行代碼塊') 18 raise AttributeError('手動(dòng)拋錯(cuò)') 19 20 print('其它內(nèi)容') #不會(huì)執(zhí)行 21 22 23 #運(yùn)行結(jié)果: 24 """ 25 出現(xiàn)with語(yǔ)句,對(duì)象的__enter__被觸發(fā),有返回值則賦值給as聲明的變量 26 raise AttributeError('手動(dòng)拋錯(cuò)') 27 =====>執(zhí)行代碼塊 28 with中代碼塊執(zhí)行完畢后執(zhí)行__exit__ 29 <class 'AttributeError'> 30 手動(dòng)拋錯(cuò) 31 AttributeError: 手動(dòng)拋錯(cuò) 32 <traceback object at 0x0000027B163221C8> 33 """ with語(yǔ)句拋錯(cuò),后面語(yǔ)句不執(zhí)行如果__exit()返回值為True,那么異常會(huì)被清空,就好像啥都沒(méi)發(fā)生一樣,with后的語(yǔ)句正常執(zhí)行
1 class Open: 2 def __init__(self, name): 3 self.name = name 4 5 def __enter__(self): 6 print('出現(xiàn)with語(yǔ)句,對(duì)象的__enter__被觸發(fā),有返回值則賦值給as聲明的變量') 7 # return self 8 9 def __exit__(self, exc_type, exc_val, exc_tb): 10 print('with中代碼塊執(zhí)行完畢后執(zhí)行__exit__') 11 print(exc_type) 12 print(exc_val) 13 print(exc_tb) 14 return True 15 16 with Open('a.txt') as f: 17 print('=====>執(zhí)行代碼塊') 18 raise AttributeError('手動(dòng)拋錯(cuò)') 19 20 print('其它內(nèi)容') 21 #運(yùn)行結(jié)果: 22 """ 23 出現(xiàn)with語(yǔ)句,對(duì)象的__enter__被觸發(fā),有返回值則賦值給as聲明的變量 24 =====>執(zhí)行代碼塊 25 with中代碼塊執(zhí)行完畢后執(zhí)行__exit__ 26 <class 'AttributeError'> 27 手動(dòng)拋錯(cuò) 28 <traceback object at 0x000001CB500921C8> 29 其它內(nèi)容 30 """ __exit__返回True,with后語(yǔ)句正常執(zhí)行用途或者說(shuō)好處:
十四、__call__
對(duì)象后面加括號(hào),觸發(fā)執(zhí)行。
注:構(gòu)造方法的執(zhí)行是由創(chuàng)建對(duì)象觸發(fā)的,即:對(duì)象 = 類名() ;而對(duì)于 __call__ 方法的執(zhí)行是由對(duì)象后加括號(hào)觸發(fā)的,即:對(duì)象() 或者 類()()
1 class Foo: 2 def __init__(self): 3 pass 4 5 def __call__(self, *args, **kwargs): 6 print('__call__') 7 8 9 obj = Foo() # 執(zhí)行 __init__ 10 obj() # 執(zhí)行 __call__ 11 Foo()() # 執(zhí)行 __call__ obj()觸發(fā)__call__十五、metaclass
1. 引子
1 class Foo: 2 pass 3 4 f1=Foo() #f1是通過(guò)Foo類實(shí)例化的對(duì)象python中一切皆是對(duì)象,類本身也是一個(gè)對(duì)象,當(dāng)使用關(guān)鍵字class的時(shí)候,python解釋器在加載class的時(shí)候就會(huì)創(chuàng)建一個(gè)對(duì)象(這里的對(duì)象指的是類而非類的實(shí)例)
上例可以看出f1是由Foo這個(gè)類產(chǎn)生的對(duì)象,而Foo本身也是對(duì)象,那它又是由哪個(gè)類產(chǎn)生的呢?
1 #type函數(shù)可以查看類型,也可以用來(lái)查看對(duì)象的類,二者是一樣的 2 print(type(f1)) # 輸出:<class '__main__.Foo'> 表示,obj 對(duì)象由Foo類創(chuàng)建 3 print(type(Foo)) # 輸出:<class 'type'>2. 什么是元類?
元類是類的類,是類的模板
元類是用來(lái)控制如何創(chuàng)建類的,正如類是創(chuàng)建對(duì)象的模板一樣
元類的實(shí)例為類,正如類的實(shí)例為對(duì)象(f1對(duì)象是Foo類的一個(gè)實(shí)例,Foo類是 type 類的一個(gè)實(shí)例)
type是python的一個(gè)內(nèi)建元類,用來(lái)直接控制生成類,python中任何class定義的類其實(shí)都是type類實(shí)例化的對(duì)象
3. 創(chuàng)建類的兩種方式
方式一:
1 class Foo: 2 def func(self): 3 print('from func')方式二:
1 def func(self): 2 print('from func') 3 4 x=1 5 Foo=type('Foo',(object,),{'func':func,'x':1}) 6 7 # type(object_or_name, bases, dict)4. 自定義元類
一個(gè)類沒(méi)有聲明自己的元類,默認(rèn)他的元類就是type,除了使用元類type,用戶也可以通過(guò)繼承type來(lái)自定義元類(順便我們也可以瞅一瞅元類如何控制類的創(chuàng)建,工作流程是什么)
?當(dāng)創(chuàng)建一個(gè)元類的對(duì)象(也是類)時(shí)的流程:
1 class Mymeta(type): 2 def __init__(self, name, bases, dic): #5 執(zhí)行__init__函數(shù),初始化對(duì)象 3 print('===>Mymeta.__init__') #6 打印'===>Mymeta.__init__' 4 5 def __new__(cls, *args, **kwargs): #2 執(zhí)行__new__函數(shù),創(chuàng)建對(duì)象 6 print('===>Mymeta.__new__') #3 打印'===>Mymeta.__new__' 7 return type.__new__(cls, *args, **kwargs) #4 返回類Mymeta創(chuàng)建的對(duì)象Foo 8 9 class Foo(object,metaclass=Mymeta): #1 類Foo是元類Mymeta的對(duì)象,在創(chuàng)建對(duì)象時(shí),會(huì)先后調(diào)用類的__new__和__init__方法 10 def __init__(self, name): 11 self.name = name 12 def __new__(cls, *args, **kwargs): 13 return object.__new__(cls) 14 15 16 17 #運(yùn)行結(jié)果: 18 # ===>Mymeta.__new__ 19 # ===>Mymeta.__init__ 創(chuàng)建元類的對(duì)象的流程,只調(diào)用元類的new和init重點(diǎn):當(dāng)通過(guò)一個(gè)類創(chuàng)建一個(gè)對(duì)象的時(shí)候,會(huì)先后調(diào)用類的__new__和__init__方法。元類是用來(lái)創(chuàng)建類的,也就是說(shuō)類是元類創(chuàng)建的對(duì)象,所以元類的__new__和__init__方法會(huì)被調(diào)用。
?當(dāng)創(chuàng)建一個(gè)元類的子類的對(duì)象(實(shí)例)時(shí)的流程:
1 #元類 2 class MyType(type): 3 4 def __init__(self, class_name, bases=None, dict=None): #5 執(zhí)行__init__函數(shù),初始化對(duì)象 5 print('MyType init --->') #6 打印:'MyType init --->' 6 print(classmethod, type(class_name)) #7 打印:<class 'classmethod'> <class 'str'> 7 print(bases) #8 打印(Foo的bases是object):(<class 'object'>,); 8 print(dict) #9 打印:對(duì)象Foo的命名空間 9 10 11 def __new__(cls, *args, **kwargs): #2 執(zhí)行__new__(MyType, *args, **kwargs)函數(shù),創(chuàng)建對(duì)象; 12 print('===>Mymeta new') #3 打印:===>Mymeta new 13 return type.__new__(cls, *args, **kwargs) #4 返回類Mymeta創(chuàng)建的對(duì)象Foo 14 15 def __call__(self, *args, **kwargs): #11 執(zhí)行__call__(Foo, *args, **kwargs) 16 print('MyType call --->', self, args, kwargs) #12 打印:MyType call ---> <class '__main__.Foo'> ('name',) {} 17 return type.__call__(self, *args, **kwargs) #13 返回由元類MyType創(chuàng)建好的對(duì)象Foo,此時(shí),就要調(diào)用Foo的__new__和__init__ 18 19 20 class Foo(object, metaclass=MyType): #1 創(chuàng)建元類的對(duì)象Foo,觸發(fā)元類MyType的__new__和__init__ 21 x = 111 22 def __init__(self, name): #17 執(zhí)行:__init__(Foo.obj, 'jack') 23 print('Foo init') #18 打印:Foo init 24 self.name = name #19 賦值:self.name = 'jack' 25 26 def __new__(cls, *args, **kwargs): #14 執(zhí)行:__new__(cls, *args, **kwargs) 27 print('Foo new') #15 打印:Foo new 28 return object.__new__(cls) #16 返回由類Foo創(chuàng)建好的對(duì)象 29 30 f = Foo('jack') #10 到這一步,Foo對(duì)象已經(jīng)創(chuàng)建好了,執(zhí)行Foo('name'),相當(dāng)于調(diào)用父類即MyType里的__call__方法,創(chuàng)建了Foo的對(duì)象,然后賦值給f 31 print(f.name) #20 打印:jack 32 33 #運(yùn)行結(jié)果: 34 # ===>Mymeta new 35 # MyType init ---> 36 # <class 'classmethod'> <class 'str'> 37 # (<class 'object'>,) 38 # {'__module__': '__main__', '__qualname__': 'Foo', 'x': 111, '__init__': <function Foo.__init__ at 0x000001F945B748C8>, '__new__': <function Foo.__new__ at 0x000001F945B74950>} 39 # MyType call ---> <class '__main__.Foo'> ('jack',) {} 40 # Foo new 41 # Foo init 42 # jack 創(chuàng)建元類的對(duì)象的對(duì)象,對(duì)象名()相當(dāng)于調(diào)用父類的call方法補(bǔ)充:
對(duì)于元類的查找,Python有一套規(guī)則:
?
?
?
參考資料:
1. http://www.cnblogs.com/linhaifeng/articles/6204014.html
2. http://bbs.csdn.net/topics/360135494
3. http://www.cnblogs.com/wilber2013/p/4695836.html
轉(zhuǎn)載于:https://www.cnblogs.com/OldJack/p/6757713.html
總結(jié)
以上是生活随笔為你收集整理的Python-面向对象进阶的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 面向对象高级(上)
- 下一篇: PetaPoco 快速上手