1. 程式人生 > >9.面向物件進階

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__()