python 類(object)的內置函數
# python 類(object)的內置函數 ### 首先 #### 以__雙下划線開頭的內置函數 __ #### __往往會在某些時候被自動調用,例如之前了解的__next__的函數,和__init__函數, 這類函數 還有一些常用的: #### 1 .isintance 2. issubclass ```python isinstance(obj , cls) class Foo (object): pass obj = Foo () isinstance(obj ,Foo) >>>True 檢查是否obj 是否是類的的對象 isubclass class Foo (object): pass class Bar(Foo) issubclass(bar,Foo) >>>True ``` #### 3.1操作獨享屬性時自動觸發 ``` __setattr__ ``` 使用點語法添加/修改屬性會觸發它的執行 ``` __delattr__ ``` 使用點語法刪除屬性的時候會觸發 ``` __getattr__ ``` 使用點語法調用屬性且屬性不存在的時候才會觸發 ``` __getattribute__ ``` 使用點語法調用屬性的時候觸發,無論屬性是否存在都會執行 ** 注意:當__getattribute__與__getattr__同時存在時,僅執行__getattribute__** ```python class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print('----> from getattr:你找的屬性不存在') def __setattr__(self, key, value): print('----> from setattr') # self.key=value #這就無限遞歸了 # self.__dict__[key]=value #應該使用它 def __delattr__(self, item): print('----> from delattr') # del self.item #無限遞歸了 self.__dict__.pop(item) #__setattr__添加/修改屬性會觸發它的執行 f1=Foo(10) print(f1.__dict__) # 因為重寫了__setattr__,凡是賦值操作都會觸發它的運行,什么都沒寫,就是根本沒有執行賦值操作,除非手動操作屬性字典,否則無法賦值 f1.z=3 print(f1.__dict__) #__delattr__刪除屬性的時候會觸發 f1.__dict__['a']=3#我們可以直接修改屬性字典,來完成添加/修改屬性的操作 del f1.a print(f1.__dict__) #__getattr__只有在使用點調用屬性且屬性不存在的時候才會觸發 f1.xxxxxx ``` ### 3.2操作對象屬性時自動觸發 ``` __setitem__ ``` 使用key的形式添加/修改屬性時觸發 ``` __getitem__ ``` 使用key的形式獲取屬性時觸發 ``` __delitem__ ``` 使用key的形式刪除屬性時觸發 ``` class Foo: def __init__(self,name): self.name=name def __getitem__(self, item): print(self.__dict__[item]) def __setitem__(self, key, value): self.__dict__[key]=value def __delitem__(self, key): print('del obj[key]時,__delitem__執行') self.__dict__.pop(key) def __delattr__(self, item): print('del obj.key時,__delattr__執行') self.__dict__.pop(item) f1=Foo('sb') f1['age']=18 f1['age1']=19 del f1.age1 del f1['age'] f1['name']='alex' print(f1.__dict__) ``` ### 4.描述符 #### 4.1什么是描述符 描述符本質就是一個類,在這個新式類中,至少實現了`__get__()`,`__set__()`,`__delete__()`中的一個, 也被稱為描述符協議 `__get__()`:調用一個屬性時,觸發 `__set__()`:為一個屬性賦值時,觸發 `__delete__()`:采用del刪除屬性時,觸發 #### 4.2為什么需要描述符: 描述符的作用是用來代理另外一個類的屬性的(必須把描述符定義成這個類的類屬性,不能定義到構造函數中),python底層的很多特性都是使用描述符來完成的,例如實例方法,`classmethod,staticmethod`等等! 簡單的說:描述符可以檢測到一個屬性的訪問和修改,從而對這些操作增加額外的功能邏輯; ``` 描述符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__) print(People.__dict__) ''' {} {'__module__': '__main__', 'name': <__main__.Str object at 0x000001CA97D1BB00>, 'age': <__main__.Int object at 0x000001CA97D1BB38>, '__init__': <function People.__init__ at 0x000001CA97D1CBF8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None} ''' #補充 print(type(p1) == People) #type(obj)其實是查看obj是由哪個類實例化來的 print(type(p1).__dict__ == People.__dict__) ''' Str設置... Int設置... ''' # 描述符應用 以及執行時機 ``` #### 4.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') ``` #### 4.4注意事項 一 描述符本身應該定義成新式類,被代理的類也應該是新式類 二 必須把描述符定義成這個類的類屬性,不能為定義到構造函數中 三 要嚴格遵循該優先級,優先級由高到底分別是 1.類屬性 2.數據描述符 3.實例屬性 4.非數據描述符 5.找不到的屬性觸發`__getattr__()` #### 4.5描述符總結 描述符是可以實現大部分python類特性中的底層魔法,包括@classmethod,@staticmethd,@property甚至是__slots__屬性 描述符是很多高級庫和框架的重要工具之一,描述符通常是使用到裝飾器或者元類的大型框架中的一個組件. 案列1:利用描述符原理完成一個自定制@property,實現延遲計算(本質就是把一個函數屬性利用裝飾器原理做成一個描述符:類的屬性字典中函數名為key,value為描述符類產生的對象) ``` class Lazyproperty: def __init__(self,func): self.func=func def __get__(self, instance, owner): print('這是我們自己定制的靜態屬性,r1.area實際是要執行r1.area()') if instance is None: return self else: print('--->') value=self.func(instance) setattr(instance,self.func.__name__,value) #計算一次就緩存到實例的屬性字典中 return value class Room: def __init__(self,name,width,length): self.name=name self.width=width self.length=length @Lazyproperty #area=Lazyproperty(area) 相當於'定義了一個類屬性,即描述符' def area(self): return self.width * self.length r1=Room('alex',1,1) print(r1.area) #先從自己的屬性字典找,沒有再去類的中找,然后出發了area的__get__方法 print(r1.area) #先從自己的屬性字典找,找到了,是上次計算的結果,這樣就不用每執行一次都去計算 實現延遲計算功能 ``` 案例2: 利用描述符原理完成一個自定制@classmethod ``` class ClassMethod: def __init__(self,func): self.func=func def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,實例來調用,instance為實例,owner為類本身, def feedback(): print('在這里可以加功能啊...') return self.func(owner) return feedback class People: name='linhaifeng' @ClassMethod # say_hi=ClassMethod(say_hi) def say_hi(cls): print('你好啊,帥哥 %s' %cls.name) People.say_hi() p1=People() p1.say_hi() #疑問,類方法如果有參數呢,好說,好說 class ClassMethod: def __init__(self,func): self.func=func def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,實例來調用,instance為實例,owner為類本身, def feedback(*args,**kwargs): print('在這里可以加功能啊...') return self.func(owner,*args,**kwargs) return feedback class People: name='linhaifeng' @ClassMethod # say_hi=ClassMethod(say_hi) def say_hi(cls,msg): print('你好啊,帥哥 %s %s' %(cls.name,msg)) People.say_hi('你是那偷心的賊') p1=People() p1.say_hi('你是那偷心的賊') 自己做一個@classmethod ``` 案例3:利用描述符原理完成一個自定制的@staticmethod ``` class StaticMethod: def __init__(self,func): self.func=func def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,實例來調用,instance為實例,owner為類本身, def feedback(*args,**kwargs): print('在這里可以加功能啊...') return self.func(*args,**kwargs) return feedback class People: @StaticMethod# say_hi=StaticMethod(say_hi) def say_hi(x,y,z): print('------>',x,y,z) People.say_hi(1,2,3) p1=People() p1.say_hi(4,5,6) 自己做一個@staticmethod ``` ### 5.再看property 一個靜態屬性property本質就是實現了get,set,delete三種方法 ``` #用法1 class Foo: @property def AAA(self): print('get的時候運行') @AAA.setter def AAA(self,value): print('set的時候運行) @AAA.deleter def AAA(self): print('delete的時候運行') #只有在屬性AAA定義property后才能定義AAA.setter,AAA.deleter f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA #==============================================用法二 class Foo: def get_AAA(self): print('get的時候運行') def set_AAA(self,value): print('set的時候運行') def delete_AAA(self): print('delete的時候運行') AAA=property(get_AAA,set_AAA,delete_AAA) #內置property三個參數與get,set,delete一一對應 f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA 用法二 ``` ### 6.對象的顯示相關函數 ``` __str__ ``` 調用str函數或者print函數時自動執行,返回值作為顯示內容 ``` __repr__ ``` 調用repr或者交互式解釋器輸出對象是自動執行,返回值作為顯示內容 注意:如果`__str__`沒有被定義,那么就會使用`__repr__`來代替輸出 這倆方法的返回值必須是字符串,否則拋出異常 ``` __format__ ``` 調用format函數時自動執行,用於定制對象的格式化輸出, format使用案例: ``` #{0.year}:{0.month}:{0.day} 這是一個格式化字符串 ,想到於"%s:%s:%s" year表示取對象的year屬性值 date_dic={ 'ymd':'{0.year}:{0.month}:{0.day}', 'dmy':'{0.day}/{0.month}/{0.year}', 'mdy':'{0.month}-{0.day}-{0.year}', } class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day def __format__(self, format_spec): if not format_spec or format_spec not in date_dic: format_spec='ymd' fmt=date_dic[format_spec] return fmt.format(self) d1=Date(2016,12,29) print(format(d1)) print('{:mdy}'.format(d1)) ``` ### 7.內存優化之`__slots__` **1.`__slots_`_是什么:** ``` 是一個類變量,變量值可以是列表,元祖,或者可迭代對象,也可以是一個字符串(意味着所有實例只有一個數據屬性) ``` **2.引子:** ``` 使用點來訪問屬性本質就是在訪問類或者對象的__dict__屬性字典(類的字典是共享的,而每個實例的是獨立的 需要給每一個實例創建一個字典) ``` **3.為何使用__slots__**: ``` 字典會占用大量內存,如果你有一個屬性很少的類,但是有很多實例,為了節省內存可以使用__slots__取代實例的__dict__ 當你定義__slots__后,__slots__就會為實例使用一種更加緊湊的內部表示,實例通過一個很小的固定大小的數組來構建,而不是為每個實例定義一個字典,這跟元組或列表很類似。 在__slots__中列出的屬性名在內部被映射到這個數組的指定下標上。 使用__slots__一個不好的地方就是我們不能再給實例添加新的屬性了,只能使用在__slots__中定義的那些屬性名。 ``` **4.注意事項:** ``` __slots__的很多特性都依賴於普通的基於字典的實現。另外,定義了__slots__后的類不再 支持一些普通類特性了,比如多繼承。大多數情況下,你應該 __slots__的很多特性都依賴於普通的基於字典的實現。另外,定義了__slots__后的類不再 支持一些普通類特性了,比如多繼承。大多數情況下,你應該只在那些經常被使用到 的用作數據結構的類上定義__slots__比如在程序中需要創建某個類的幾百萬個實例對象 。 關於__slots__的一個常見誤區是它可以作為一個封裝工具來防止用戶給實例增加新的屬性。盡管使用__slots__可以達到這樣的目的,但是這個並不是它的初衷。更多的是用來作為一個內存優化工具。 ``` 案例: ``` class Foo: __slots__=['name','age'] f1=Foo() f1.name='alex' f1.age=18 print(f1.__slots__) #f1.y=2 報錯 print(f1.__slots__) #f1不再有__dict__ f2=Foo() f2.name='egon' f2.age=19 print(f2.__slots__) print(Foo.__dict__) #f1與f2都沒有屬性字典__dict__了,統一歸__slots__管,節省內存 ``` ### 8.迭代器協議之 `__next__`和`__iter__` 關於迭代器之前已經詳細的探討過 這里簡單回顧一下: ``` class Foo: def __init__(self,start,stop): self.num=start self.stop=stop def __iter__(self): return self def __next__(self): if self.num >= self.stop: raise StopIteration n=self.num self.num+=1 return n f=Foo(1,5) from collections import Iterable,Iterator print(isinstance(f,Iterator)) for i in Foo(1,5): print(i) ```1,2,3,4, ``` ### 9.幫助文檔`__doc__` 這是一個隱藏屬性,用於獲取類的幫助文檔,其實就是類下面的多行注釋 ``` class Foo: '我是描述信息' pass class Bar(Foo): pass print(Foo.__doc__) #輸出 我是描述 print(Bar.__doc__) #該屬性無法繼承給子類 #輸出 None ``` 需要注意的是,該屬性不會被繼承 ### 10.`__module__和__class__` `__module__` 表示當前操作的對象在那個模塊 `__class__` 表示當前操作的對象的類是什么 ``` class C: def __init__(self): self.name = 'SB' #該類位於lib/aa.py文件中 ``` 在另一個文件中: ``` from lib.aa import C obj = C() print obj.__module__ # 輸出 lib.aa,即:輸出模塊 print obj.__class__ # 輸出 lib.aa.C,即:輸出類 ``` ### 11. `__del__`析構方法 #### 什么是析構方法 析構看做構建的反義詞,構建指一個東西從無到有,析構指一個東西從有到無 析構方法的特點是: 當對象在內存中被釋放時,會自動觸發執行。 #### 為什么需要析構方法 如果產生的對象僅僅只是python程序級別的(用戶級),那么無需定義`__del__`,因為python會自動完成所有資源的回收; 如果產生的對象的同時還會向操作系統發起系統調用,即一個對象有用戶級與內核級兩種資源,比如(打開一個文件,創建一個數據庫鏈接),則必須在清除對象的同時回收系統資源,這就用到了`__del__` #### 使用案例: ``` class Foo: def __del__(self): print('執行我del啦') f1=Foo() del f1 print('------->') #輸出 執行我del啦 #輸出 -------> class Foo: def __del__(self): print('執行我del啦') f1=Foo() # del f1 print('------->') #輸出 -------> #輸出 執行我del啦 ``` 你會發現就算不去調用`del`方法一樣會出發執行`__del__`,這是因為你python解釋器在程序運行結束時必須將所有資源全部釋放,當然包括創建的f1對象; #### 典型的應用場景: 創建數據庫類,用該類實例化出數據庫鏈接對象,對象本身是存放於用戶空間內存中,而鏈接則是由操作系統管理的,存放於內核空間內存中 當程序結束時,python只會回收自己的內存空間,即用戶態內存,而操作系統的資源則沒有被回收,這就需要我們定制__del__,在對象被刪除前向操作系統發起關閉數據庫鏈接的系統調用,回收資源 這與文件處理是一個道理: ``` f=open('a.txt') #做了兩件事,在用戶空間拿到一個f變量,在操作系統內核空間打開一個文件 del f #只回收用戶空間的f,操作系統的文件還處於打開狀態 #所以我們應該在del f之前保證f.close()執行,即便是沒有del,程序執行完畢也會自動del清理資源,於是文件操作的正確用法應該是 f=open('a.txt') #讀寫... f.close() #很多情況下大家都容易忽略f.close,這就用到了with上下文管理 ``` ### 12.上下文管理之`__enter__`和`__exit__` #### 什么是上下文管理? 上下文指的是一種語境,屬於語言科學,說起來很抽象,其實你已經在很多地方使用到他了,來看一個實例: ``` with open('a.txt') as f: print(f.read()) ``` 在這個代碼中python解釋器分析出你的代碼想要做的事情,然后在結束的時候自動幫你將資源釋放了,with中的所有代碼都在一個上下文中,你可以把他理解為一個代碼范圍 #### 為什么需要上下文管理 上面的例子可以看出不使用上下文管理完沒有問題,需要程序員,在合適的位置編寫代碼來關閉文件資源,這其實是一種體力活完全沒有技術含量; 所以使用上下文可以省略掉一些重復代碼的編寫工作,同時避免了一些粗心的程序員忘記做一些清理工作; #### 如何使用 該協議包含兩個方法 `__enter__` 出現`with`語句,對象的`__enter__`被觸發,有返回值則賦值給as聲明的變量 `__exit__` with中代碼塊執行完畢時執行 只要這個一個類實現了這兩個方法就可以被with 語句使用 案例: 模擬open ``` class Open: def __init__(self,filepath,mode='r',encoding='utf-8'): self.filepath=filepath self.mode=mode self.encoding=encoding def __enter__(self): # print('enter') self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) return self.f def __exit__(self, exc_type, exc_val, exc_tb): # print('exit') self.f.close() return True def __getattr__(self, item): return getattr(self.f,item) with Open('a.txt','w') as f: print(f) f.write('aaaaaa') f.wasdf #拋出異常,交給__exit__處理 ``` **需要注意的是:** 1. `` __exit__()`中的三個參數分別代表異常類型,異常值和追溯信息 2. with語句中代碼塊出現異常時,會立即觸發方法`__exit__`的執行,並將異常信息錯誤參數傳入 3. with語句中代碼塊未出現異常正常結束時也會觸發方法`__exit__`的執行,此時參數中的異常信息為空 4. 如果`__exit__()`返回值為True,那么異常會被清空,就好像啥都沒發生一樣,with后的語句正常執行 **總結:** 1.使用with語句的目的就是把代碼塊放入with中執行,with結束后,自動完成清理工作,無須手動干預 2.在需要管理一些資源比如文件,網絡連接和鎖的編程環境中,可以在__exit__中定制自動釋放資源的機制,你無須再去關系這個問題,這將大有用處 ### 13`__call__` `__call__`是一個函數,在對象被調用時執行,調用就是加括號() 注:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對於 **call** 方法的執行是由對象后加括號觸發的,即:對象() 或者 類()的區別 ``` class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 執行 __init__ obj() # 執行 __call__ ```