標籤:span exp 編程 實現 init 列印 open() %s 優先
一、
__call__
對象後面加括弧,觸發執行類下面的__call__方法。
建立對象時,對象 = 類名() ;而對於 __call__ 方法的執行是由對象後加括弧觸發的,即:對象() 或者 類()()
class Foo: ? def __call__(self, *args, **kwargs): print("我執行啦") ? f = Foo() f() #對象加括弧調用執行類下的__call__方法 #輸出結果 我執行啦
二、
__next__和
__iter__實現迭代器協議
迭代器協議是指:對象必須提供一個next方法,執行該方法要麼返回迭代中的下一項,要麼就引起一個StopIteration異常,以終止迭代 (只能往後走不能往前退)
可迭代對象執行obj.__iter__()得到的結果就是迭代器對象。
在類中,如果有__iter__和__next__內建方法,那麼就構成了迭代器。
例子
class Foo: ? def __init__(self,n): self.n = n ? def __iter__(self): return self #執行個體本身就是迭代對象,故返回自己 ? def __next__(self): if self.n >10: raise StopIteration #如果超過10就報StopIteration 錯誤 self.n = self.n + 1 return self.n ? f = Foo(7) for i in f: #for迴圈自動調用__next__方法,實現了迭代取值 print(i)
例子2
輸出100內的斐契那波數列
class F: ? def __init__(self): self.a = 0 self.b = 1 ? def __iter__(self): return self ? def __next__(self): self.a ,self.b = self.b , self.a + self.b if self.a > 100: raise StopIteration return self.a ? f = F() for i in f: print(i)
三、描述符(
__get__,__set__,__delete__)
描述符(descriptor):
1、描述符本質
就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱為描述符協議。__get__():調用一個屬性時,觸發__set__():為一個屬性賦值時,觸發__delete__():採用del刪除屬性時,觸發
2、描述符的作用
是用來代理另外一個類的屬性的(必須把描述符定義成這個類的類屬性,不能定義到建構函式中)
描述符是在另外一個類的類屬性進行定義的,描述符在一個類的類屬性__dict__字典裡
例子1
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() ? def __init__(self,name): self.name = name ? ? b = Bar("nick") b.x #調用執行描述符裡的__get__方法 print(b.x) # b.x = 1 # 調用執行描述符裡的__set__方法 print(b.__dict__) del b.x #調用執行描述符裡的__delete__方法 print(b.__dict__)
輸出結果
執行了__get__ 執行了__get__ None 執行了__set__ {‘name‘: ‘nick‘} 執行了__delete__ {‘name‘: ‘nick‘}
例子2
#描述符Str class Str: def __get__(self, instance, owner): print(‘Str調用‘) def __set__(self, instance, value): print(‘Str設定...‘) def __delete__(self, instance): print(‘Str刪除...‘) ? #描述符Int class Int: def __get__(self, instance, owner): print(‘Int調用‘) def __set__(self, instance, value): print(‘Int設定...‘) def __delete__(self, instance): print(‘Int刪除...‘) ? class People: name=Str() age=Int() def __init__(self,name,age): #name被Str類代理,age被Int類代理, self.name=name self.age=age ? #何地?:定義成另外一個類的類屬性 ? #何時?:且看下列示範 ? p1=People(‘alex‘,18) ? #描述符Str的使用 p1.name p1.name=‘egon‘ del p1.name ? #描述符Int的使用 p1.age p1.age=18 del p1.age ? #我們來瞅瞅到底發生了什麼 print("__p1.__dict__",p1.__dict__) print(People.__dict__) ? #補充 print(type(p1) == People) #type(obj)其實是查看obj是由哪個類執行個體化來的 print(type(p1).__dict__ == People.__dict__)
輸出結果
Str設定... Int設定... Str調用 Str設定... Str刪除... Int調用 Int設定... Int刪除... __p1.__dict__ {} {‘__module__‘: ‘__main__‘, ‘name‘: <__main__.Str object at 0x021C6850>, ‘age‘: <__main__.Int object at 0x021C6870>, ‘__init__‘: <function People.__init__ at 0x021C5DB0>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘People‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘People‘ objects>, ‘__doc__‘: None} True True
3、描述符分兩種
(1) 資料描述符:至少實現了__get__()和__set__()
? class Foo: def __set__(self, instance, value): print(‘set‘) def __get__(self, instance, owner): print(‘get‘)
(2) 非資料描述符:沒有實現__set__()
class Foo: def __get__(self, instance, owner): print(‘get‘)
注意:非資料描述符一般是只有__get__,如果保留__delete__執行會報錯。
4、 注意事項:
(1)描述符本身應該定義成新式類,被代理的類也應該是新式類(python3中全部是新式類)
(2)必須把描述符定義成另外一個類的類屬性,不能為定義到建構函式中,
(3)要嚴格遵循該優先順序,優先順序由高到底分別是
a.類屬性b.資料描述符c.執行個體屬性d.非資料描述符e.找不到的屬性觸發__getattr__()
例子1
class Foo: ? def __get__(self, instance, owner): print("執行了__get__") ? def __set__(self, instance, value): print("執行了__set__") ? def __delete__(self, instance): print("執行了__delete__") ? class People: ? name = Foo() ? def __init__(self,name): self.name = name ? ? p = People("nick") People.name = "nick" #調用執行了描述符的__set__方法,這一步類屬性由之前的描述符被定義成另外一個字串, # 所以下面再次調用就無法再次使用描述符了 People.name ? #可以得出結論,類屬性的優先順序大於資料描述符
例子2
class Foo: ? def __get__(self, instance, owner): print("執行了__get__") ? def __set__(self, instance, value): print("執行了__set__") ? def __delete__(self, instance): print("執行了__delete__") ? class People: ? name = Foo() ? def __init__(self,name): self.name = name ? ? p = People("nick") #執行個體化對象,調用資料描述符的__set__, # 但是由於描述符的__set__只是執行了列印操作,什麼都沒做,所以p對象的__dict__什麼都沒有 p.name = "nicholas" print(p.__dict__) #輸出的結果為空白 ? #因此可以得出結論,資料描述符的優先順序大於執行個體屬性(字典操作)
例子3
class Foo(object): def __init__(self): pass ? def __get__(self, instance, owner): print("執行了__get__") ? class People(object): ? name = Foo("x") ? def __init__(self,name,age): self.name = name self.age = age ? ? ? p = People("nick",18) #執行個體化對象,這裡由於是非資料描述符,優先順序低於執行個體屬性, # 所以這裡直接設定了執行個體屬性,而不再調用描述符 print(p.name) #列印直接輸出執行個體屬性 print(p.__dict__) #輸出的結果:{‘name‘: ‘nick‘, ‘age‘: 18} ? #因此可以得出結論,執行個體屬性的優先順序大於非資料描述符
例子4
class Foo(object): def __init__(self,name2): self.name2 = name2 ? def __get__(self, instance, owner): print("執行了__get__") ? ? class People(object): ? name = Foo("x") ? def __init__(self,name,age): self.name = name self.age = age ? def __getattr__(self, item): print("__getattr__") ? ? p = People("nick",18) #執行個體化對象,這裡由於是非資料描述符,優先順序低於執行個體屬性, # 所以這裡直接設定了執行個體屬性,而不再調用描述符 print(p.name) print(p.sex) #調用不存在的屬性執行了__getattr__ print(p.__dict__) #輸出的結果:{‘name‘: ‘nick‘, ‘age‘: 18}
5、描述符的應用
例子1
class Type: ? def __init__(self,key,expect_type): self.key = key self.expect_type = expect_type ? def __get__(self, instance, owner): print("執行__get__方法") print(self) #這裡的self就是type類的對象 print(instance) #這裡的instance就是傳入的People類的對象 print("執行__get__方法") return instance.__dict__[self.key] #通過instance的字典擷取對象的屬性值 ? def __set__(self, instance, value): print("執行__set__方法") instance.__dict__[self.key] = value #instance是另一個類的對象,這裡要設定對象的屬性字典 ? def __delete__(self, instance): print("執行__delete__方法") instance.__dict__.pop(self.key) #刪除對象的屬性 ? class People: name = Type("name",str) age = Type("age",int) ? def __init__(self,name,age): self.name = name self.age = age ? p1 = People("nick",18) #調用2次描述符,對對象的字典進行設定 print(p1.name) #通過資料描述符擷取對象的屬性值 print(p1.__dict__) p1.age = 20 #調用描述符對對象進行設定 print(p1.__dict__)
輸出結果
執行__set__方法 執行__set__方法 執行__get__方法 <__main__.Type object at 0x004CB4F0> <__main__.People object at 0x02106DF0> 執行__get__方法 nick {‘name‘: ‘nick‘, ‘age‘: 18} 執行__set__方法 {‘name‘: ‘nick‘, ‘age‘: 20}
?
例子2
class Type: ? def __init__(self,key,expect_type): self.key = key self.expect_type = expect_type ? def __get__(self, instance, owner): print("執行__get__方法") print(self) #這裡的self就是type類的對象 print(instance) #這裡的instance就是傳入的People類的對象 print("執行__get__方法") return instance.__dict__[self.key] #通過instance的字典擷取對象的屬性值 ? def __set__(self, instance, value): print("執行__set__方法") if not isinstance(value,self.expect_type): print("您輸入的%s不是%s"%(self.key,self.expect_type)) raise TypeError instance.__dict__[self.key] = value #instance是另一個類的對象,這裡要設定對象的屬性字典 ? def __delete__(self, instance): print("執行__delete__方法") instance.__dict__.pop(self.key) #刪除對象的屬性 ? class People: name = Type("name",str) age = Type("age",int) ? def __init__(self,name,age): self.name = name self.age = age ? p1 = People("nick",18) #調用2次描述符,對對象的字典進行設定 print(p1.name) #通過資料描述符擷取對象的屬性值 print(p1.__dict__) p1.age = 20 #調用描述符對對象進行設定 print(p1.__dict__) # p1.name = 11 #通過描述符的if not isinstance(value,self.expect_type)判斷屬性的類型 ? # p2 = People(88,18) #通過描述符的if not isinstance(value,self.expect_type)判斷屬性的類型
四、
__enter__和
__exit__
開啟檔案操作用 with open() as f操作,這叫做上下文管理協議,即with語句,為了讓一個對象相容with語句,必須在這個對象的類中聲明__enter__和__exit__方法。
__enter__(self):當with開始啟動並執行時候觸發此方法的運行
__exit__(self, exc_type, exc_val, exc_tb):當with運行結束之後觸發此方法的運行
exc_type如果拋出異常,這裡擷取異常的類型
exc_val如果拋出異常,這裡顯示異常內容
exc_tb如果拋出異常,這裡顯示所在位置
用途或者說好處:
1.使用with語句的目的就是把代碼塊放入with中執行,with結束後,自動完成清理工作,無須手動幹預
2.在需要管理一些資源比如檔案,網路連接和鎖的編程環境中,可以在__exit__中定製自動釋放資源的機制,你無須再去關係這個問題,這將大有用處
例子
class OPEN: def __init__(self,name): self.name = name def __enter__(self): print("執行__enter__") return self def __exit__(self, exc_type, exc_val, exc_tb): print("執行__exit__") print(exc_type) print(exc_val) print(exc_tb) print("執行__exit__2222")with OPEN("a.txt") as f: print(f) #執行列印__enter__內建方法,同時列印內建方法返回的結果#with 語句結束時執行__exit__方法,沒有錯誤則列印None,有錯誤則列印錯誤的資訊print("上下文管理協議")
Python之路(第二十七篇) 物件導向進階:內建方法、描述符