9.面向物件進階
一.isinstance(obj,cls)和issubclass(sub,super)
(1)isinstance(obj,cls)檢查一個物件obj是否是一個類cls的例項
(2)issubclass(sub,super)檢查sub類是否是super類的派生類
class Foo: #定義一個類Foo pass class Bar(Foo): #定義一個類Bar繼承了Foo pass f1=Foo() #例項化得到f1這個物件 print(isinstance(f1,Foo)) #f1是物件,Foo是類,isinstance判斷f1這個物件是否由Foo這個類例項化而來 print(issubclass(Bar,Foo)) #Bar是物件,Foo是類,issubclass判斷Bar是否是Foo的子類
返回:
True
True
二.__getattr__和__getattribute__二者同時出現,只會執行__getattribute__,除非__getattribute__在執行中丟擲異常AttributeError
class Foo: def __init__(self,x): self.x=x def __getattr__(self, item): #是在__getattribute__裡丟擲AttributeError異常的時候才會觸發__getattr__的執行 print('執行的是getattr') def __getattribute__(self, item): #不管屬性找得到找不到首先觸發__getattribute__執行,屬性存在會從當前例項屬性字典裡返回想要的值,如果不存在會報異常 print('執行的是getattribute') raise AttributeError('丟擲異常了') #AttributeError這句話模擬python自動丟擲的異常會把指令發給__getattr__,如果沒有__getattr__程式會崩掉 f1=Foo(10) f1.xxxxxx #呼叫不存在的屬性xxxxxx
返回:
執行的是getattribute
執行的是getattr
三.__setitem__,__getitem__,__delitem__跟字典相關的查詢,賦值,刪除
class Foo: #定義一個類 def __getitem__(self, item): #查詢 print('getitem',item) return self.__dict__[item] #self呼叫他的屬性字典__dict__,[item]找那個屬性 def __setitem__(self, key, value): #賦值 print('setitem') self.__dict__[key]=value #self呼叫他的屬性字典__dict__,[key]就是中括號傳過來的值name,value是xixi def __delitem__(self, key): #刪除 print('delitem') self.__dict__.pop(key) #self呼叫他的屬性字典__dict__,pop(key)指定刪除 f1=Foo() #例項化得到f1這個物件 print(f1.__dict__) #檢視f1例項字典,沒定義init方法f1字典裡是空:{} #以字典方式賦值 f1['name']='xixi' #會觸發 __setitem__函式執行--->f1.__dict__['name']='xixi' print(f1.__dict__) #檢視f1例項字典,賦值成功:'name': 'xixi'} #以字典方式查詢: print(f1['name']) #可以調到name的value返回:getitem name 和 xixi #以字典方式刪除 del f1['name'] print(f1.__dict__) #返回:delitem 和 {}
列印:
{}
setitem
{'name': 'xixi'}
getitem name
xixi
delitem
{}
四.__str__和__repr__用來控制輸出,輸出的解法都是自己return的值,return的值必須是字串
1.__str__函式或者print函式實際觸發例項下面的__str__()方法
class Foo: def __init__(self,name,age): #init方法傳name,age self.name=name self.age=age def __str__(self): #__str__呼叫的是f1.__str__()方法,必須要return個值 return '名字是%s 年齡是%s' %(self.name,self.age) #可以自己控制str的列印的資訊 f1=Foo('xixi',18) #例項化傳倆個引數 #方式一: print(f1) #實際觸發的是系統提供的str(f1)方法的執行,str(f1)執行的是f1.__str__() #方式二: x=str(f1) print(x) #方式三: x=f1.__str__() print(x)
返回:
名字是xixi 年齡是18
名字是xixi 年齡是18
名字是xixi 年齡是18
2.__repr__或者互動直譯器實際觸發例項下面的__repr__()方法
class Foo: def __init__(self,name,age): #init方法傳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('xixi',18) #例項化傳倆個引數 print(f1) #如果有str先找str(f1)下的f1.__str__(),如果找不到str直接找f1.__repr__()作為一個替代品
返回:
名字是xixi 年齡是18
五.__format__自定製格式化方法
format_dic={ #自定製格式化倆種日期輸出格式 'ymd':'{0.year}{0.mon}{0.day}', #'ymd'對應的是'{0.year}{0.mon}{0.day}'格式 'y-m-d':'{0.year}-{0.mon}-{0.day}', #'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): #自定義__format__方法,必須有返回值字串 print('開始執行') print('--->',format_spec) #format_spec沒有傳引數就是空 if not format_spec or format_spec not in format_dic: #判斷format_spec這個值如果是空not format_spec或者不在字典的key裡format_spec not in format_dic format_spec = 'ymd' #給一個預設的格式是ymd fm=format_dic[format_spec] #有了format_spec這個值就可以從字典裡取到想要的值,fm就是取到了'{0.year}{0.mon}{0.day}'後面的格式 return fm.format(self) #拿到格式就可以fm.format相當於.format(d1) d1=Date(2018,10,'08') print(format(d1,'ymd')) #format把d1傳給self,把ymd傳給format_spec print(format(d1,'y-m-d')) #format把d1傳給self,把y-m-d傳給format_spec print('不傳format_spec列印預設格式',format(d1)) print('傳的format_spec不在format_dic字典裡列印預設格式',format(d1,'aaaa'))
返回:
開始執行
---> ymd
20181008
開始執行
---> y-m-d
2018-10-08
開始執行
--->
不傳format_spec列印預設格式 20181008
---> aaaa
傳的format_spec不在format_dic字典裡列印預設格式 20181008
六.__slots__屬性
1.是一個類變數,變數值可以是列表,元祖,或者可迭代物件,也可以是一個字串(意味著所有例項只有一個數據屬性)
2.使用點來訪問屬性本質就是在訪問類或者物件的__dict__屬性字典(類的字典是共享的,而每個例項的是獨立的)
3.為何使用__slots__:字典會佔用大量記憶體,如果你有一個屬性很少的類,但是有很多例項,為了節省記憶體可以使用__slots__取代例項的__dict__
4.當你使用__slots__後,__slots__就會為例項使用一種更加緊湊的內部表示。例項通過一個很小的固定大小的陣列來構建,而不是為每個例項定義一個字典,這個跟元祖列表很類似
5.缺點:不能在給例項新增新的屬性,只能使用在__slots__中定義的那些屬性名
class Foo: __slots__=['name','age'] #定義字串類似屬性字典的形式{'name':None,'age':None},只是定義了key,由這個類產生的例項不再具有__dict__屬性字典 f1=Foo() #由Foo生成的例項只能有倆個定義的屬性['name','age'] f1.name='xixi' print(f1.name) f1.age=18 print(f1.__slots__) #顯示f1可以定義的屬性['name', 'age'],就是print(Foo.__slots__)類的屬性 #f1.gender='male' #給f1加一個沒有的屬性gender會報錯 f2=Foo() #例項化f2 print(f2.__slots__) #f2沒有類屬性只有__slots__,f2也只能定義倆個屬性['name', 'age'] f2.name='shishi' f2.age=18 print(f2.name) print(f2.age)
列印結果:
xixi
['name', 'age']
['name', 'age']
shishi
18
七.__doc__屬性:在每一個類裡面會加一個這個東西,只要調就是從自己的位置調
class Foo: pass class Bar(Foo): pass print(Bar.__doc__) #該屬性無法繼承給子類 print(Bar.__doc__) #該屬性無法繼承給子類
返回:
None
None
八.__module__和__class__
__module__表示當前操作的物件在那個模組
__class__表示當前操作的物件的類是什麼
lib目錄下有一個定義aa模組
class C: def __init__(self): self.name = 'xixi'
呼叫aa模組
from lib.xx import C #呼叫lib目錄下有一個aa模組,aa模組裡有一個C類 c1=C() #例項化 print(c1.name) #可以呼叫到 print(c1.__module__) #檢視c1來自那個模組 print(c1.__class__) #檢視c1來自那個類產生的
返回:
xixi
lib.xx
<class 'lib.xx.C'>
九__del__析構方法
當物件在記憶體中被釋放時,自動觸發執行
class Foo: def __init__(self,name): self.name=name def __del__(self): print('我執行啦') f1=Foo('xixi') #由Foo產生個例項傳name #del f1 #刪除例項會觸發__del__ #del f1.name #刪除例項的屬性不會觸發__del__ print('--------------------->') #程式執行完畢會自動回收記憶體,觸發__del__
返回:
--------------------->
我執行啦
十.__call__
物件後面加括號,觸發執行。
class Foo: #建立一個類Foo def __call__(self, *args, **kwargs): #定義__call__方法 print('例項執行obj()') f1=Foo() #Foo()得到一個例項f1,Foo觸發的call方法返回的例項 f1() #f1加小括號調的是的這個物件Foo類下的__call__ #Foo() #Foo也是一個物件Foo加小括號調的是的abc類下的__call__
返回:
例項執行obj()
十一.__next__和__iter__實現迭代器協議
class Foo: #建立一個Foo類 def __init__(self,n): #創造建構函式可以傳值n self.n=n # __iter__ def __iter__(self): #加上__iter__方法變成把一個物件變成一個可迭代物件 return self #返回self #__next__ def __next__(self): #必須有一個next方法 if self.n == 9: #當n=9 raise StopIteration('終止') #StopIteration終止迴圈 self.n+=1 #每次n這個值自增 return self.n #返回自增1這個值 f1=Foo(5) #得到f1這個物件 #for迴圈 for i in f1: #for迴圈f1這個物件實際上先把這個物件變成f1.__iter__(),每次得到的結果是可迭代物件obj print(i) #每次迴圈就是在呼叫obj下面的.__next_()方法
返回:
6
7
8
9
迭代器協議實現斐波那契數列
class Fib: #建立一個類Fib def __init__(self): #創造建構函式 self.a=1 #起始值a self.b=1 #起始值b def __iter__(self): #定義__iter__方法 return self #return自己 def __next__(self): #定義 __next__方法 if self.a > 50: #當self.a大於50 raise StopIteration('終止了') #退出迴圈 self.a,self.b=self.b,self.a + self.b #self.a等於self.b,self.b等於self.a + self.b倆個相加的結果 return self.a #返回self.a f1=Fib() #得到f1這個物件 print(next(f1)) print(next(f1)) print('==================================') for i in f1: #for迴圈從3以後開始 print(i)
返回:
1
2
==================================
3
5
8
13
21
34
55
十二.描述符(__get__,__set__,__delete__)
1.描述符是什麼:描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()這三個方法中的一個,這也被稱為描述符協議
__get__()呼叫一個屬性時觸發
__set__()為一個屬性賦值時觸發
__delete__()採用del刪除屬性時觸發
2.描述符的作用是用來代理另外一個類的屬性的(必須把描述符定義成這個類的屬性,不能定義到建構函式)
class Foo: #定義新式類 def __get__(self, instance, owner): #定義__get__方法:一個物件呼叫屬性的時候會觸發 print('===>get方法') def __set__(self, instance, value): #定義__set__方法 print('===>set方法') def __delete__(self, instance): #定義__delete__方法 print('===>delete方法') #描述符必須要有另外一個類屬性裡定義才會觸發 class Bar: #定義一個Bar類 x=Foo() #在Bar類中定義一個類屬性,這個類屬性的值是Foo例項化的結果,類屬性就是描述符的一個物件 b1=Bar() #b1通過Bar得到一個例項 #呼叫 b1.x #b1點x呼叫x這個屬性就是Foo就可以觸發描述符裡的__get__方法:===>get方法 #賦值 b1.x=1 #b1點x=1呼叫x=1這個屬性就是Foo就可以觸發描述符裡賦值的__set__方法:'===>set方法 #刪除 del b1.x #del b1點x呼叫x這個屬性就是Foo就可以觸發描述符裡賦值的__delete__方法:'===>delete方法
返回:
===>get方法
===>set方法
===>delete方法
set方法的詳解
class Foo: def __set__(self, instance, value): print('===>set方法',instance,value) #列印一下 instance.__dict__['x']=value #操作例項下的屬性字典進行了一個真正的賦值操作 class Bar: x=Foo() #第三步:觸發Foo()的 __set__方法 def __init__(self,n): self.x=n #第二步:相當於b1.x=10,x這個屬性被代理了,賦值觸發的是x=Foo() b1=Bar(10) #第一步:例項化操作self.x=n(這步例項化的過程相當於找的是 __set__) print(b1.__dict__) #修改x的值 b1.x=7777777 print(b1.__dict__)
返回:
===>set方法 <__main__.Bar object at 0x005747B0> 10
{'x': 10}
===>set方法 <__main__.Bar object at 0x005747B0> 7777777
{'x': 7777777}
3.描述符分倆種
(1)資料描述符:至少實現了__get__()和__set__()
class Foo: #定義新式類 def __get__(self, instance, owner): #定義__get__方法: print('===>get方法') def __set__(self, instance, value): #定義__set__方法 print('===>set方法')
(2)非資料描述符:沒有實現__set__()
class Foo: #定義新式類 def __get__(self, instance, owner): #定義__get__方法: print('===>get方法')
4.注意:
(1)描述符本身應該定義成新式類,被代理的類也應該是新式類
(2)必須把描述符定義成這個類的屬性,不能為定義到建構函式中
(3)要嚴格遵循該優先順序,優先順序由高到底分別是:類屬性--->資料描述符--->例項屬性--->非資料描述符--->找不到的屬性出發__getatter__()