一、反射
1 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力(自省)。這一概念的提出很快引發(fā)了計(jì)算機(jī)科學(xué)領(lǐng)域關(guān)于應(yīng)用反射性的研究。它首先被程序語言的設(shè)計(jì)領(lǐng)域所采用,并在Lisp和面向?qū)ο蠓矫嫒〉昧顺煽儭?
?
2 python面向?qū)ο笾械姆瓷洌和ㄟ^字符串的形式操作對象相關(guān)的屬性。python中的一切事物都是對象(都可以使用反射)
四個(gè)可以實(shí)現(xiàn)自省的函數(shù)
下列方法適用于類和對象(一切皆對象,類本身也是一個(gè)對象)
導(dǎo)入其他模塊,利用反射查找該模塊是否存在某個(gè)方法
四種方法使用效果展示:
class BlackMedium: feature = ' Ugly ' def __init__ (self,name,addr): self.name = name self.addr = addr def sell_house(self): print ( ' %s 黑中介賣房子啦,傻逼才買呢,但是誰能證明自己不傻逼 ' % self.name) def rent_house(self): print ( ' %s 黑中介租房子啦,傻逼才租呢 ' % self.name) b1 =BlackMedium( ' 萬成置地 ' , ' 回龍觀天露園 ' ) # 檢測是否含有某屬性 print (hasattr(b1, ' name ' )) print (hasattr(b1, ' sell_house ' )) # 獲取屬性 n=getattr(b1, ' name ' ) print (n) func =getattr(b1, ' rent_house ' ) func() # getattr(b1,'aaaaaaaa') #報(bào)錯(cuò) print (getattr(b1, ' aaaaaaaa ' , ' 不存在啊 ' )) # 設(shè)置屬性 setattr(b1, ' sb ' ,True) setattr(b1, ' show_name ' , lambda self:self.name+ ' sb ' ) print (b1. __dict__ ) print (b1.show_name(b1)) # 刪除屬性 delattr(b1, ' addr ' ) delattr(b1, ' show_name ' ) delattr(b1, ' show_name111 ' ) # 不存在,則報(bào)錯(cuò) print (b1. __dict__ )
3 為什么用反射之反射的好處
有倆程序員,一個(gè)simon,一個(gè)是zhurui,simon在寫程序的時(shí)候需要用到zhurui所寫的類,但是zhurui去跟女朋友度蜜月去了,還沒有完成他寫的類,simon想到了反射,使用了反射機(jī)制simon可以繼續(xù)完成自己的代碼,等zhurui度蜜月回來后再繼續(xù)完成類的定義并且去實(shí)現(xiàn)simon想要的功能。
總之反射的好處就是,可以事先定義好接口,接口只有在被完成后才會真正執(zhí)行,這實(shí)現(xiàn)了即插即用,這其實(shí)是一種‘后期綁定’,什么意思?即你可以事先把主要的邏輯寫好(只定義接口),然后后期再去實(shí)現(xiàn)接口的功能
class FtpClient: ' ftp客戶端,但是還么有實(shí)現(xiàn)具體的功能 ' def __init__ (self,addr): print ( ' 正在連接服務(wù)器[%s] ' % addr) self.addr =addr
# from module import FtpClient f1=FtpClient( ' 192.168.1.1 ' ) if hasattr(f1, ' get ' ): func_get =getattr(f1, ' get ' ) func_get() else : print ( ' ---->不存在此方法 ' ) print ( ' 處理其他的邏輯 ' )
好處二:動(dòng)態(tài)導(dǎo)入模塊(基于反射當(dāng)前模塊成員)
二、 __setattr__,__delattr__,__getattr__
三者用法展示:
# #getattr
# class Foo: # x=1 # def __init__(self,y): # self.y=y # # def __getattr__(self, item): # print('執(zhí)行__getattr__') # # f1=Foo(10) # print(f1.y) # print(getattr(f1,'y')) #len(str) ------>str.__len__() ##delattr # # class Foo: # x=1 # def __init__(self,y): # self.y=y # # def __delattr__(self, item): # print('刪除操作__delattr__') # # f1=Foo(10) # del f1.y # print(f1.x) # #setattr 添加/修改屬性會觸發(fā) class Foo: x =1 def __init__ (self,y): self.y = y def __setattr__ (self,key,value): print ( ' __setattr__執(zhí)行 ' ) # self.key=value self. __dict__ [key]= value f1 =Foo(10 ) print (f1. __dict__ ) f1.z =2 print (f1. __dict__ )
三、包裝標(biāo)準(zhǔn)類型
包裝:python為大家提供了標(biāo)準(zhǔn)數(shù)據(jù)類型,以及豐富的內(nèi)置方法,其實(shí)在很多場景下我們都需要基于標(biāo)準(zhǔn)數(shù)據(jù)類型來定制我們自己的數(shù)據(jù)類型,新增/改寫方法,這就用到了我們剛學(xué)的繼承/派生知識 其他的標(biāo)準(zhǔn)類型均可以通過下面的方式進(jìn)行二次加工
class List(list): def append(self,p_object): if type(p_object) is str: # self.append(p_object) super().append(p_object) else : print ( ' 只能添加字符串類型 ' ) def show_middle(self): mid_index =int(len(self)/2 ) return self[mid_index] # l2=List('hello world') # print(l2,type(l2)) l1 =List( ' helloworld ' ) # print(l1,type(l1)) l1.append(123456 ) l1.append( ' sb ' ) print (l1)
?
四、isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)檢查是否obj是否是cls的對象
class Foo(object): pass obj = Foo() isinstance(obj, Foo)
issubclass(sub, super)檢查sub類是否是 super 類的派生類
class Foo: pass class Bar(Foo): pass f1 = Foo() print (isinstance(f1,Foo)) print (issubclass(Bar,Foo))
五、__getattribute__
class Foo: def __init__ (self,x): self.x = x def __getattr__ (self, item): print ( ' 執(zhí)行的是getattr ' ) def __getattribute__ (self, item): print ( ' 執(zhí)行的是getattribute ' ) raise AttributeError( ' 拋出異常了 ' ) f1 =Foo(11 ) # f1.x f1.xxxxxx # 不存在的屬性訪問,觸發(fā)__getattr__
六、__setitem__,__getitem__,__delitem__
class Foo: def __getitem__ (self, item): print ( ' getitem ' ) # retun self.__dict__ def __setitem__ (self, key, value): print ( ' setitem ' ) self. __dict__ [key]= value def __delitem__ (self, key): print ( ' delitem ' ) self. __dict__ .pop(key) f1 = Foo() print (f1. __dict__ ) # f1.name='simon' f1[ ' name ' ]= ' simon ' f1[ ' age ' ]=28 print ( ' ===========> ' ,f1. __dict__ ) # del f1.name # print(f1.__dict__) # print(f1.age) del f1[ ' name ' ] print (f1. __dict__ )
七、__str__,__repr__
# ####str####### class Foo: def __init__ (self,name,age): self.name = name self.age = age def __str__ (self): return ' 名字是【%s】 年齡是【%s】 ' % (self.name,self.age) f1 =Foo( ' simon ' ,18 ) print (f1) # --str(f1)-->f1.__str__() x= str(f1) print (x) # #####repr######## ,當(dāng)str與repr共存是,優(yōu)先使用str class Foo: def __init__ (self,name,age): self.name = name self.age = age # def __str__(self): # return '這是str' def __repr__ (self): return ' 名字是【%s】 年齡是【%s】 ' % (self.name,self.age) f1 =Foo( ' simon ' ,20 ) # repr(f1) ----->f1.__repr__() print (f1) # str(f1) ---->> f1.__str__() ------>f1.__repr__()
備注:
''' str函數(shù)或者print函數(shù)--->obj.__str__() repr或者交互式解釋器--->obj.__repr__() 如果__str__沒有被定義,那么就會使用__repr__來代替輸出 注意:這倆方法的返回值必須是字符串,否則拋出異常 '''
八、__format__
format_dic= { ' ymd ' : ' {0.year}{0.mon}{0.day} ' , ' m-d-y ' : ' {0.mon}-{0.day}-{0.year} ' , ' y:m:d ' : ' {0.year}:{0.mon}:{0.day} ' } class Date: def __init__ (self,year,mon,day): self.year = year self.mon = mon self.day = day def __format__ (self, format_spec): print ( ' 我要執(zhí)行啦 ' ) print ( ' -------> ' ,format_spec) if not format_spec or format_spec not in format_dic: format_spec = ' ymd ' fm = format_dic[format_spec] return fm.format(self) d1 =Date(2018,12,30 ) format(d1) # d1.__format__() print (format(d1)) print (format(d1, ' ymd ' )) print (format(d1, ' y:m:d ' )) print (format(d1, ' m-d-y ' )) print (format(d1, ' fsdrerewr ' ))
九、__slots__
1. __slots__是什么 :是一個(gè)類變量,變量值可以是列表,元祖,或者可迭代對象,也可以是一個(gè)字符串(意味著所有實(shí)例只有一個(gè)數(shù)據(jù)屬性) 2 .引子:使用點(diǎn)來訪問屬性本質(zhì)就是在訪問類或者對象的__dict__屬性字典(類的字典是共享的,而每個(gè)實(shí)例的是獨(dú)立的) 3 .為何使用__slots__:字典會占用大量內(nèi)存,如果你有一個(gè)屬性很少的類,但是有很多實(shí)例,為了節(jié)省內(nèi)存可以使用__slots__取代實(shí)例的__dict__ 當(dāng)你定義__slots__后, __slots__就會為實(shí)例使用一種更加緊湊的內(nèi)部表示 。實(shí)例通過一個(gè)很小的固定大小的數(shù)組來構(gòu)建,而不是為每個(gè)實(shí)例定義一個(gè) 字典,這跟元組或列表很類似。在__slots__中列出的屬性名在內(nèi)部被映射到這個(gè)數(shù)組的指定小標(biāo)上。使用__slots__一個(gè)不好的地方就是我們不能再給 實(shí)例添加新的屬性了,只能使用在__slots__中定義的那些屬性名。 4.注意事項(xiàng): __slots__的很多特性都依賴于普通的基于字典的實(shí)現(xiàn) 。另外,定義了__slots__后的類不再 支持一些普通類特性了,比如多繼承。大多數(shù)情況下,你應(yīng)該 只在那些經(jīng)常被使用到 的用作數(shù)據(jù)結(jié)構(gòu)的類上定義__slots__比如在程序中需要?jiǎng)?chuàng)建某個(gè)類的幾百萬個(gè)實(shí)例對象 。 關(guān)于__slots__的一個(gè)常見誤區(qū)是它可以作為一個(gè)封裝工具來防止用戶給實(shí)例增加新的屬性。盡管使用__slots__可以達(dá)到這樣的目的,但是這個(gè)并不是它的初衷。 更多的是用來作為一個(gè)內(nèi)存優(yōu)化工具。
class Foo: __slots__ =[ ' name ' , ' age ' ] f1 = Foo() f1.name = ' alex ' f1.age =18 print (f1. __slots__ ) f2 = Foo() f2.name = ' egon ' f2.age =19 print (f2. __slots__ ) print (Foo. __dict__ ) # f1與f2都沒有屬性字典__dict__了,統(tǒng)一歸__slots__管,節(jié)省內(nèi)存
十、__doc__
class Foo: ' 我是描述信息 ' pass class Bar(Foo): pass # print(Foo.__doc__) #該屬性無法繼承給子類 print (Bar. __dict__ ) print (Bar. __doc__ ) # 該屬性無法繼承給子類
十一、__module__和__class__
__module__表示當(dāng)前操作的對象在哪個(gè)模塊
—class__ 表示當(dāng)前操作的對象的類是什么
lib/simon.py
# !/usr/bin/env python # _*_ coding:utf-8 _*_ class C: def __init__ (self): self.name = ' simon '
aa.py
from lib.simon import C obj = C() print (obj. __module__ ) # 輸出 lib.simon,即:輸出模塊 print (obj. __class__ ) # 輸出lib.simon.C,即:輸出類
十二、__del__
class Foo: def __init__ (self,name): self.name = name def __del__ (self): print ( ' 我執(zhí)行啦 ' ) f1 =Foo( ' simon ' ) # del f1 #刪除實(shí)例會觸發(fā)__del__ del f1.name # 刪除實(shí)例的屬性不會觸發(fā)__del__ print ( ' ---------------------> ' ) # 程序運(yùn)行完畢會自動(dòng)回收內(nèi)存,觸發(fā)__del__
十三、__call__
對象后面加括號,觸發(fā)執(zhí)行。
注:構(gòu)造方法的執(zhí)行是由創(chuàng)建對象觸發(fā)的,即:對象 = 類名() ;而對于 __call__ 方法的執(zhí)行是由對象后加括號觸發(fā)的,即:對象() 或者 類()()
class Foo: def __call__ (self, *args, ** kwargs): print ( ' 實(shí)例執(zhí)行啦 obj() ' ) f1 = Foo() f1() # f1的類Foo 下的__call__ Foo() # Foo的類 xxx下的__call__
十四、__next__和__iter__實(shí)現(xiàn)迭代器協(xié)議
1、迭代器協(xié)議是指:對象必須提供一個(gè)Next方法,執(zhí)行該方法要么返回迭代中的下一項(xiàng),要么就引起一個(gè)StopIteration異常,以終止迭代(只能往后走不能往前退)
2、可迭代對象:實(shí)現(xiàn)了迭代器協(xié)議的對象(如何實(shí)現(xiàn):對象內(nèi)部定義一個(gè)__iter__()方法)
3、協(xié)議是一種約定,可迭代對象實(shí)現(xiàn)了迭代器協(xié)議,python內(nèi)部工具(如for循環(huán),sum,min,max函數(shù)等)使用迭代器協(xié)議訪問對象。
class Foo: def __init__ (self,n): self.n = n def __iter__ (self): return self def __next__ (self): if self.n == 13 : raise StopIteration( ' 終止了 ' ) self.n +=1 return self.n # l=list('simon') # for i in l: # print(i) f1 =Foo(10 ) # print(f1.__next__()) # print(f1.__next__()) # print(f1.__next__()) for i in f1: # iter(f1)------------>f1.__iter__() print (i)
斐波那契數(shù)列:
class Fib: def __init__ (self): self._a =1 self._b =1 def __iter__ (self): return self def __next__ (self): if self._a > 100 : raise StopIteration( ' 終止了 ' ) self._a,self._b =self._b,self._a + self._b return self._a f1 = Fib() print (next(f1)) print (next(f1)) print (next(f1)) print (next(f1)) print (next(f1)) print ( ' ============================ ' ) for i in f1: print (i)
十五、描述符(_get_,_set_,_delete_)
1、什么是描述符
描述符本質(zhì)就是一個(gè)新式類,在這個(gè)新式類中,至少實(shí)現(xiàn)了__get__(),__set__(),__delete__()中的一個(gè),這也被稱為描述符協(xié)議
__get__():調(diào)用一個(gè)屬性時(shí),觸發(fā)
__set__():為一個(gè)屬性賦值時(shí),觸發(fā)
__delete__():采用del刪除屬性時(shí),觸發(fā)
class Foo: def __get__ (self, instance, owner): print ( ' =====>get方法 ' ) def __set__ (self, instance, value): print ( ' =====>set方法 ' ) def __delete__ (self, instance): print ( ' =====>delete方法 ' ) class Bar: x =Foo() # 在何地? # 在何時(shí) b1= Bar() b1.x b1.x =1 del b1.x f1 = Foo() f1.name = ' simon ' print (f1.name)
類屬性>數(shù)據(jù)描述符
# 描述符Str class Str: def __get__ (self, instance, owner): print ( ' Str調(diào)用 ' ) def __set__ (self, instance, value): print ( ' Str設(shè)置... ' ) def __delete__ (self, instance): print ( ' Str刪除... ' ) class People: name = Str() def __init__ (self,name,age): # name被Str類代理,age被Int類代理, self.name= name self.age = age # 基于上面的演示,我們已經(jīng)知道,在一個(gè)類中定義描述符它就是一個(gè)類屬性,存在于類的屬性字典中,而不是實(shí)例的屬性字典 # 那既然描述符被定義成了一個(gè)類屬性,直接通過類名也一定可以調(diào)用吧,沒錯(cuò) People.name # 恩,調(diào)用類屬性name,本質(zhì)就是在調(diào)用描述符Str,觸發(fā)了__get__() People.name = ' egon ' # 那賦值呢,我去,并沒有觸發(fā)__set__() del People.name # 趕緊試試del,我去,也沒有觸發(fā)__delete__() # 結(jié)論:描述符對類沒有作用-------->傻逼到家的結(jié)論 ''' 原因:描述符在使用時(shí)被定義成另外一個(gè)類的類屬性,因而類屬性比二次加工的描述符偽裝而來的類屬性有更高的優(yōu)先級 People.name #恩,調(diào)用類屬性name,找不到就去找描述符偽裝的類屬性name,觸發(fā)了__get__() People.name='egon' #那賦值呢,直接賦值了一個(gè)類屬性,它擁有更高的優(yōu)先級,相當(dāng)于覆蓋了描述符,肯定不會觸發(fā)描述符的__set__() del People.name #同上 '''
數(shù)據(jù)描述符>實(shí)例屬性
# 描述符Str class Str: def __get__ (self, instance, owner): print ( ' Str調(diào)用 ' ) def __set__ (self, instance, value): print ( ' Str設(shè)置... ' ) def __delete__ (self, instance): print ( ' Str刪除... ' ) class People: name = Str() def __init__ (self,name,age): # name被Str類代理,age被Int類代理, self.name= name self.age = age p1 =People( ' egon ' ,18 ) # 如果描述符是一個(gè)數(shù)據(jù)描述符(即有__get__又有__set__),那么p1.name的調(diào)用與賦值都是觸發(fā)描述符的操作,于p1本身無關(guān)了,相當(dāng)于覆蓋了實(shí)例的屬性 p1.name= ' egonnnnnn ' p1.name print (p1. __dict__ ) # 實(shí)例的屬性字典中沒有name,因?yàn)閚ame是一個(gè)數(shù)據(jù)描述符,優(yōu)先級高于實(shí)例屬性,查看/賦值/刪除都是跟描述符有關(guān),與實(shí)例無關(guān)了 del p1.name
實(shí)例屬性>非數(shù)據(jù)描述符
class Foo: def func(self): print ( ' 我胡漢三又回來了 ' ) f1 = Foo() f1.func() # 調(diào)用類的方法,也可以說是調(diào)用非數(shù)據(jù)描述符 # 函數(shù)是一個(gè)非數(shù)據(jù)描述符對象(一切皆對象么) print (dir(Foo.func)) print (hasattr(Foo.func, ' __set__ ' )) print (hasattr(Foo.func, ' __get__ ' )) print (hasattr(Foo.func, ' __delete__ ' )) # 有人可能會問,描述符不都是類么,函數(shù)怎么算也應(yīng)該是一個(gè)對象啊,怎么就是描述符了 # 笨蛋哥,描述符是類沒問題,描述符在應(yīng)用的時(shí)候不都是實(shí)例化成一個(gè)類屬性么 # 函數(shù)就是一個(gè)由非描述符類實(shí)例化得到的對象 # 沒錯(cuò),字符串也一樣 f1.func = ' 這是實(shí)例屬性啊 ' print (f1.func) del f1.func # 刪掉了非數(shù)據(jù) f1.func()
再次驗(yàn)證:實(shí)例屬性>非數(shù)據(jù)描述符
class Foo: def __set__ (self, instance, value): print ( ' set ' ) def __get__ (self, instance, owner): print ( ' get ' ) class Room: name = Foo() def __init__ (self,name,width,length): self.name = name self.width = width self.length = length # name是一個(gè)數(shù)據(jù)描述符,因?yàn)閚ame=Foo()而Foo實(shí)現(xiàn)了get和set方法,因而比實(shí)例屬性有更高的優(yōu)先級 # 對實(shí)例的屬性操作,觸發(fā)的都是描述符的 r1=Room( ' 廁所 ' ,1,1 ) r1.name r1.name = ' 廚房 ' class Foo: def __get__ (self, instance, owner): print ( ' get ' ) class Room: name = Foo() def __init__ (self,name,width,length): self.name = name self.width = width self.length = length # name是一個(gè)非數(shù)據(jù)描述符,因?yàn)閚ame=Foo()而Foo沒有實(shí)現(xiàn)set方法,因而比實(shí)例屬性有更低的優(yōu)先級 # 對實(shí)例的屬性操作,觸發(fā)的都是實(shí)例自己的 r1=Room( ' 廁所 ' ,1,1 ) r1.name r1.name = ' 廚房 '
?總結(jié):
1、描述符本身應(yīng)該定義成新式類,被代理的類也應(yīng)該是新式類
2、必須把描述符定義成這個(gè)類的代理,不能為定義到構(gòu)造函數(shù)中
3、要嚴(yán)格遵循優(yōu)先級,優(yōu)先級由高到低分別
?
十六、__enter__和__exit__
操作文件對象的時(shí)候:
with open( ' text.txt ' , ' r ' ) as f: ' 代碼塊 '
上述叫做上下文管理協(xié)議,即with語句,為了讓一個(gè)對象兼容with語句,必須在這個(gè)對象的類中聲明__enter__和__exit__方法
class Foo: def __init__ (self,name): self.name = name def __enter__ (self): print ( ' 執(zhí)行enter ' ) return self def __exit__ (self, exc_type, exc_val, exc_tb): print ( ' 執(zhí)行exit ' ) # f=Open('a.txt') with Foo( ' a.txt ' ) as f: print (f) print (f.name) print ( ' 00000000000000000000 ' )
上述代碼分析:
class Foo: def __init__ (self,name): self.name = name def __enter__ (self): print ( ' 執(zhí)行enter ' ) return self def __exit__ (self, exc_type, exc_val, exc_tb): print ( ' 執(zhí)行exit ' ) print (exc_type) print (exc_val) print (exc_tb) return True # f=Open('a.txt') with Foo( ' a.txt ' ) as f: print (f) print (asfdreevergewafa) # 觸發(fā)__exit__ print (f.name) print ( ' 00000000000000000000 ' ) # with obj as f: ' 代碼塊 ' 1、with obj ---->>觸發(fā)obj. __enter__ (),拿到返回值 2、as f--------->f= 返回值、 3、with obj as f 等同于 f=obj. __enter__ () 4 、執(zhí)行代碼塊 一:沒有異常的情況下,整個(gè)代碼塊運(yùn)行完畢后去觸發(fā)__exit__,它的三個(gè)參數(shù)都為None 二:有異常的情況下,從異常出現(xiàn)的位置直接觸發(fā)__exit__ a: 如果__exit__的返回值為True,代表吞掉了異常 b: 如果__exit__的返回值不為True,代表了吐出了異常 c: __exit__的運(yùn)行完畢就代表了整個(gè)with語句的執(zhí)行完畢
總結(jié):
1.使用with語句的目的就是把代碼塊放入with中執(zhí)行,with結(jié)束后,自動(dòng)完成清理工作,無須手動(dòng)干預(yù)
2.在需要管理一些資源比如文件,網(wǎng)絡(luò)連接和鎖的編程環(huán)境中,可以在__exit__中定制自動(dòng)釋放資源的機(jī)制,你無須再去關(guān)系這個(gè)問題,這將大有用處
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
