1. isinstance(obj, cls)
判斷第一個參數是不是第二個參數的實例對象,返回布爾值。第一個參數爲對象,第二個參數爲類。python
class Animal: pass class Dog(Animal): pass d1 = Dog() print(isinstance(d1, Dog)) # True print(isinstance(d1, Animal)) # True # 也能夠和 type 同樣用來判斷類型 print(isinstance('python', str)) # True
在繼承關係中,一個對象的數據類型是某個子類,那麼它也能夠被看做是父類,反之則不行。程序員
2. issubclass(cls, class_or_tuple))
判斷第一個參數是不是第二個參數的子類,返回布爾值。編程
class Animal: pass class Dog(Animal): pass print(issubclass(Dog, Animal)) # True
__getattribute__
__getattribute__(object, name)
在獲取對象屬性時,不論是否存在都會觸發。數組
class Animal: def __init__(self, name): self.name = name def __getattribute__(self, item): print('觸發 getattribute') a1 = Animal('小黑') a1.color # 觸發 getattribute a1.name # 觸發 getattribute
與 __getattr__
同時存在時,只會觸發 __getattribute__
,除非拋出 AttributeError
異常(其餘異常不行):緩存
class Animal: def __init__(self, name): self.name = name def __getattr__(self, item): print('觸發 getattr') def __getattribute__(self, item): print('觸發 getattribute') raise AttributeError('觸發') # 使用異常處理 a1 = Animal('小黑') a1.color ------------ 觸發 getattribute 觸發 getattr
這裏咱們使用異常處理 raise 模擬 Python內部機制,拋出 AttributeError,使得 __getattr__
也會被觸發。網絡
__getitem__、__setitem__、__delitem__
與 __getattr__、__setattr__、__delattr__
相似,不一樣之處在於它是以字典形式訪問、修改或刪除,而且只要訪問、修改或刪除就會觸發。app
class Animal: def __getitem__(self, item): print('__getitem__') return self.__dict__[item] def __setitem__(self, key, value): print('__setitem__') self.__dict__[key] = value def __delitem__(self, item): print('__delitem__') self.__dict__.pop(item) a1 = Animal() print(a1.__dict__) # {} # set a1['name'] = 'rose' # __setitem__ print(a1.__dict__) # {'name': 'rose'} # get print(a1['name']) # __getitem__ rose # del del a1['name'] # __delitem__ print(a1.__dict__) # {}
__str__、__repr__
__str__ 和 __repr__
都是用來定製對象字符串顯示形式框架
# L = list('abcd') # print(L) # ['a', 'b', 'c', 'd'] # f = open('a.txt', 'w') # print(f) # <_io.TextIOWrapper name='a.txt' mode='w' encoding='cp936'> class Animal: pass a1 = Animal() print(a1) # <__main__.Animal object at 0x00000000053E5358> 標準的對象字符串格式
Python 中一切皆對象,L、f 和 a1 都是對象,可是它們的顯示形式倒是不同。這是由於 Python 內部對不一樣的對象定製後的結果。咱們能夠重構 __str__ 或 __repr__
方法,可以更友好地顯示:
重構 __str__
函數
class Animal: pass def __str__(self): return '自定製對象的字符串顯示形式' a1 = Animal() print(a1) m = str(a1) # print() 的本質是在調用 str(),而 str() 的本質是在調用 __str__() print(m) print(a1.__str__()) # obj.__str__() --------- # 自定製對象的字符串顯示形式 # 自定製對象的字符串顯示形式 # 自定製對象的字符串顯示形式
能夠看出,咱們重構 __str__
方法後,返回的是咱們定製的字符串。而 print() 和 str() 的本質都是在調用 object.__str__()
。工具
重構 __repr__
class Animal: pass def __repr__(self): return 'repr' a1 = Animal() print(a1) # repr 返回 repr print(a1.__repr__()) # repr
沒有重構 __repr__
方法時,輸出 a1,結果是對象內存地址。當構建了後,輸出 repr。
當 __str__
沒定義時,用 __repr__
代替輸出。當二者都存在時,__str__
覆蓋 __repr__
。
二者區別:
__str__
,若類自己沒有定義則使用__repr__
代替輸出,一般是一個友好的顯示。__repr__
適用於全部其餘環境(包括解釋器),程序員在開發期間一般使用它。__format__
用於類中自定製 format。
s = '{0}{0}{0}'.format('rose') print(s) # 'roseroserose'
重構 __format__
# 定義格式化字符串格式字典 date_dic = { 'ymd': '{0.year}-{0.month}-{0.day}', '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' fm = date_dic[format_spec] return fm.format(self) d1 = Date(2018, 11, 28) # x = '{0.year}-{0.month}-{0.day}'.format(d1) # print(x) # 打印 2018-11-28 print(format(d1, 'ymd')) # 2018-11-28 print(format(d1, 'mdy')) # 11-28-2018 print(format(d1)) # 當爲空時,輸出默認格式 ymd
format 實際調用的是 __format__()
__slots__
__slots__
實質是一個類變量,它能夠是字符串、列表、元組也能夠是個可迭代對象。
它的做用是用來覆蓋類的屬性字典 __dict__
。咱們都知道類和對象都有本身獨立的內存空間,每產生一個實例對象就會建立一個屬性字典。當產生成千上萬個實例對象時,佔用的內存空間是很是可怕的。
當咱們用定義 __slots__
後,實例便只能經過一個很小的固定大小的數組來構建,而不是每一個實例都建立一個屬性字典,從而到達節省內存的目的。
class Animal: # __slots__ = 'name' __slots__ = ['name', 'age'] pass a1 = Animal() # # print(a1.__dict__) # AttributeError: 'Animal' object has no attribute '__dict__' # a1.name = 'rose' # print(a1.__slots__) # name # print(a1.name) # rose a1.name = 'lila' # 設置類變量的值 a1.age = 1 print(a1.__slots__) # ['name', 'age'] print(a1.name, a1.age) # lila 1
Tips:
__dict__
方法,所以依賴 __dict__
的操做,如新增屬性會報沒有這個屬性,所以要慎用。__doc__
用於查看類的文檔字符串,不能繼承。若類沒有定義文檔字符串,則返回 None(Python 內部自定義的)。
class Animal: """動物類""" pass class Dog(Animal): pass print(Animal.__doc__) print(Dog.__doc__) print(Dog.__dict__) ---------- # 動物類 # None # {'__module__': '__main__', '__doc__': None}
__module__ 和 __class__
用於查看當前操做對象是在哪一個模塊裏和哪一個類裏。
# a.py class Cat: name = 'tom' pass
# b.py from a import Cat c1 = Cat() print(c1.__module__) # a print(c1.__class__) # Cat
__del__
析構方法,當對象再內存中被釋放時(如對象被刪除),自動觸發。
Python 有自動回收機制,無需定義 __del__
。可是若產生的對象還調用了系統資源,即有用戶級和內核級兩種資源,那麼在使用完畢後須要用到 __del__
來回收系統資源。
class Foo: def __init__(self, name): self.name = name def __del__(self): print('觸發 del') f1 = Foo('rose') # del f1.name # 不會觸發 del f1 # 會觸發 print('-------->') # --------> # 觸發 del # 觸發 del # -------->
__call__
當對象加上括號後,觸發執行它。若沒有類中沒有重構 __call__
,則會報 not callable
。
class Animal: def __call__(self): print('對象%s被執行' % self) a1 = Animal() a1() ------------------- # 對象<__main__.Animal object at 0x00000000053EC278>被執行
Python 一切皆對象,類也是個對象。而類加括號能調用執行,那麼它的父類(object)中必然也會有個 __call__
方法。
__iter__ 和 __next__ 實現迭代器協議
__next__或next()
方法,執行該方法可返回迭代項的下一項,直到超出邊界,引起 StopIteration 終止迭代。__iter__
方法。__inter__
方法,它可以自動捕捉 StopIteration 異常,所以超出邊界也不會報錯。所以可迭代對象內部必須有一個 __iter__ 和 __next__
方法。
class Dog: def __init__(self, age): self.age = age d1 = Dog(1) for i in d1: print(i) # TypeError: 'Dog' object is not iterable
對象 d1 中沒有 __iter__()
全部它是不可迭代對象
class Dog: def __init__(self, age): self.age = age def __iter__(self): # return None # iter() returned non-iterator of type 'NoneType' return self # 須要返回可迭代類型 def __next__(self): if self.age >= 6: raise StopIteration('終止迭代~') self.age += 1 return self.age d1 = Dog(1) # print(d1.__next__()) # 2 # print(d1.__next__()) # 3 for i in d1: # obj = d1.__iter__() print(i) # obj.__next__() ---------- # 2 # 3 # 4 # 5 # 6
對象 d1 實現 __iter__ 和 __next__
後變成可迭代對象。
class Fib: def __init__(self, n): self._x = 0 self._y = 1 self.n = n def __iter__(self): return self def __next__(self): self._x, self._y = self._y, self._x + self._y if self._x > self.n: raise StopIteration('終止') return self._x f1 = Fib(13) for i in f1: print(i) ------------------ # 1 # 1 # 2 # 3 # 5 # 8 # 13
描述符就是將某種特殊類型的類的實例指派給另外一個類的屬性,這兩個類必須都是新式類,並且這個特殊類至少實現了 __get__()、__set__()、__delete__()
中的一種。
魔法方法 | 含義 |
---|---|
__get__(self, instance, owner) |
調用屬性時觸發,返回屬性值 |
__set__(self, instance, value) |
爲一個屬性賦值時觸發,不返回任何內容 |
__delete__(self, instance) |
採用 del 刪除屬性時觸發,不返回任何內容 |
class Descriptor: def __get__(self, instance, owner): print(self, instance, owner) def __set__(self, instance, value): print(self, instance, value) def __delete__(self, instance): print(self, instance) class Test: x = Descriptor() # 類 Descriptor 是類 Test 的類屬性 x
描述符是用來代理另外一個類的類屬性,而不能定義到構造函數中去。那麼它是在哪裏以什麼樣的條件才能觸發呢?
事實上它只能在它所描述的類中,當其實例訪問、修改或刪除類屬性時纔會觸發:
t1 = Test() print(t1.x) # 訪問 print(t1.__dict__) t1.x = 10 # 修改 print(t1.__dict__) # 凡是與被代理的類屬性相關的操做,都是找描述符,所以代理類的實例屬性字典爲空。 print(Test.__dict__) # 查看 Test 的屬性字典
<__main__.Descriptor object at 0x0000000004F217B8> <__main__.Test object at 0x0000000004F217F0> <class '__main__.Test'> None {} <__main__.Descriptor object at 0x0000000004F217B8> <__main__.Test object at 0x0000000004F217F0> 10 {} {'__module__': '__main__', 'x': <__main__.Descriptor object at 0x0000000004F217B8>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
能夠看出實例 t1 在調用、修改屬性 x 時,都會自動觸發 __get__ 和 __set__
方法 ,刪除也是同樣。查看 Test 的屬性字典,發現其屬性 x 的值是 Descriptor 的實例。
描述符分兩種:
__get__() 和 __set__()
__set__()
。Tips:
__getattr__()
。類屬性 > 數據描述符
class Descriptor: def __get__(self, instance, owner): print('get') def __set__(self, instance, value): print('set') def __delete__(self, instance): print('delete') class Test: x = Descriptor() t1 = Test() Test.x # 調用數據屬性 print(Test.__dict__) # 'x': <__main__.Descriptor object at 0x0000000004F21EF0> Test.x = 1 # 設置類屬性 print(Test.__dict__) # 'x': 1
get {'__module__': '__main__', 'x': <__main__.Descriptor object at 0x0000000004F21EF0>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None} {'__module__': '__main__', 'x': 1, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
上面例子中,描述符被定義爲類屬性,所以在調用時,觸發了描述符中的 __get__()
方法。而賦值操做,直接將類屬性的 x 的值由原來而的描述符賦值爲 1,所以沒有被觸發。
問題:爲何實例賦值操做會觸發 __set__()
方法?
由於實例的屬性字典中自己沒有屬性 x,它只能在類中查找。賦值操做,操做的是本身的屬性字典,而不會改變類屬性 x。
類屬性 > 數據描述符 > 實例屬性
t1 = Test() Test.x = 10000 t1.x
結果爲空,什麼都沒觸發。這是由於類屬性 x 被修改,覆蓋了數據描述符,所以實例屬性也不能觸發。
實例屬性 > 非數據描述符
class Descriptor: def __get__(self, instance, owner): print('get') class Test: x = Descriptor() t1 = Test() t1.x = 1 print(t1.__dict__) # {'x': 1}
非數據描述符,沒有 __set__()
方法。所以在修改實例屬性時,不會觸發 __set__()
方法,而是修改了本身的屬性字典。
非數據描述符 > __getattr__
class Descriptor: def __get__(self, instance, owner): print('get') class Test: x = Descriptor() def __getattr__(self, item): print('觸發 getattr') t1 = Test() t1.x = 1 # 調用實例屬性 print(t1.__dict__) t1.y # 調用不存在的實例屬性 ------------------- # {'x': 1} # 觸發 getattr
從上能夠看出,先進行了調用了非數據描述符。再觸發 __getattr__
。可是由於實例屬性大於非數據描述符,所以先看到的是實例的屬性字典。
Python 是弱語言類型,在變量定義時不須要指定其類型,類型之間也能夠互相輕易轉換,但這也會致使程序在運行時類型錯誤致使整個程序崩潰。Python 內部沒有相應的類型判斷機制,所以咱們有以下需求:
自定製一個類型檢查機制,在用戶輸入的類型與預期不符時,提示其類型錯誤。
現有一個 Dog 類,想要實現用戶輸入的名字只能是字符串,年齡只能是整型,在輸入以前對其進行判斷:
class Descriptor: def __init__(self, key, expected_type): self.key = key self.expected_type = expected_type def __get__(self, instance, owner): print('get') return instance.__dict__[self.key] def __set__(self, instance, value): print('set') print(value) if isinstance(value, self.expected_type): instance.__dict__[self.key] = value else: raise TypeError('你輸入的%s不是%s' % (self.key, self.expected_type)) class Dog: name = Descriptor('name', str) # age = Descriptor('age', int) def __init__(self, name, age, color): self.name = name self.age = age self.color = color d1 = Dog('rose', 2, 'white') # 觸發 d1.__set__() print(d1.__dict__) d1.name = 'tom' print(d1.__dict__) --------- # set # rose # {'name': 'rose', 'age': 2, 'color': 'white'} # set # tom # {'name': 'tom', 'age': 2, 'color': 'white'}
類 Descriptor 是 Dog 的修飾符(修飾類變量 name、age),當d1 = Dog('rose', 2, 'white')
,觸發 d1.__set__()
方法,也就會調用對象 d1 的屬性字典,進行賦值操做。
當用戶輸入的名字不是字符串時:
d2 = Dog(56, 2, 'white') ------ # set # 56 # --------------------------------------------------------------------------- # TypeError Traceback (most recent call last) # <ipython-input-36-a60d3743b7a0> in <module>() # 33 # print(d1.__dict__) # 34 # ---> 35 d2 = Dog(56, 2, 'white') # <ipython-input-36-a60d3743b7a0> in __init__(self, name, age, color) # 22 # age = Descriptor('age', int) # 23 def __init__(self, name, age, color): # ---> 24 self.name = name # 25 self.age = age # 26 self.color = color # <ipython-input-36-a60d3743b7a0> in __set__(self, instance, value) # 15 instance.__dict__[self.key] = value # 16 else: # ---> 17 raise TypeError('你輸入的%s不是%s' % (self.key, self.expected_type)) # 18 # 19 # TypeError: 你輸入的name不是<class 'str'>
直接報錯,提示用戶輸入的不是字符串,同理 age 也是同樣。這樣就簡單地實現了定義變量以前的類型檢查。
使用 with 語句操做文件對象,這就是上下文管理協議。要想一個對象兼容 with 語句,那麼這個對象的類中必須實現 __enter__ 和 __exit__
。
class Foo: def __init__(self, name): self.name = name def __enter__(self): print('next') return self def __exit__(self,exc_type, exc_val, exc_tb): print('觸發exit') with Foo('a.txt') as f: # 觸發 __enter__() print(f.name) # a.txt,打印完畢,執行 __exit__() print('------>') --------- # next # a.txt # 觸發exit # ------>
with Foo('a.txt') as f
觸發 __enter__()
,f 爲對象自己。執行完畢後觸發 __exit__()
。
__exit__()
的三個參數
在沒有異常的時候,這三個參數皆爲 None:
class Foo: ... def __exit__(self, exc_type, exc_val, exc_tb): print('觸發exit') print(exc_type) print(exc_val) print(exc_tb) with Foo('a.txt') as f: print(f.name) ------ # a.txt # 觸發exit # None # None # None
有異常的狀況下,三個參數分別表示爲:
__exit__()
返回值爲 None 或空時:
class Foo: ... def __exit__(self, exc_type, exc_val, exc_tb): print('觸發exit') print(exc_type) print(exc_val) print(exc_tb) with Foo('a.txt') as f: print(f.name) print(dkdkjsjf) # 打印存在的東西,執行 __exit__(),完了退出整個程序 print('--------->') # 不會被執行 print('123') # 不會被執行
aa.txt 觸發exit <class 'NameError'> name 'dkdkjsjf' is not defined <traceback object at 0x0000000004E62F08> --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-39-9fe07cc1dbe0> in <module>() 13 with Foo('a.txt') as f: 14 print(f.name) ---> 15 print(dkdkjsjf) 16 print('--------->') 17 print('123') NameError: name 'dkdkjsjf' is not defined
with 語句運行觸發 __enter__()
返回,並得到返回值賦值給 f。遇到異常觸發 __exit__()
,並將錯誤信息賦值給它的三個參數,整個程序結束,with 語句中出現異常的語句將不會被執行。
__exit__()
返回值爲 True 時:
class Foo: ... def __exit__(self, exc_type, exc_val, exc_tb): print('觸發exit') print(exc_type) print(exc_val) print(exc_tb) return True with Foo('a.txt') as f: print(f.name) print(dkdkjsjf) print('--------->') print('123')
a.txt 觸發exit <class 'NameError'> name 'dkdkjsjf' is not defined <traceback object at 0x0000000004E5AA08> 123
當 __exit__()
返回值爲 True 時,with 中的異常被捕獲,程序不會報錯。__exit__()
執行完畢後,整個程序不會直接退出,會繼續執行剩餘代碼,可是 with 語句中異常後的代碼不會被執行。
好處:
__exit__()
中定製自動釋放資源的機制。總結:
__enter__()
,拿到返回值並賦值給 f。__exit__()
,其返回值爲三個 None。__exit__
:
__exit__
返回值爲 true,那麼直接捕獲異常,程序不報錯 。且繼續執行剩餘代碼,但不執行 with 語句中異常後的代碼。__exit__
返回值不爲 True,產生異常,整個程序執行完畢。Python 中一切皆對象,裝飾器一樣適用於類:
無參數裝飾器
def foo(obj): print('foo 正在運行~') return obj @foo # Bar=foo(Bar) class Bar: pass print(Bar())
foo 正在運行~ <__main__.Bar object at 0x0000000004ED1B70>
有參數裝飾器
利用裝飾器給類添加類屬性:
def deco(**kwargs): # kwargs:{'name':'tom', 'age':1} def wrapper(obj): # Dog、Cat for key, val in kwargs.items(): setattr(obj, key, val) # 設置類屬性 return obj return wrapper @deco(name='tom', age=1) # @wrapper ===> Dog=wrapper(Dog) class Dog: pass print(Dog.__dict__) # 查看屬性字典 print(Dog.name, Dog.age) @deco(name='john') class Cat: pass print(Cat.__dict__) print(Cat.name)
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None, 'name': 'tom', 'age': 1} tom 1 {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Cat' objects>, '__weakref__': <attribute '__weakref__' of 'Cat' objects>, '__doc__': None, 'name': 'john'} john
@deco(name='tom', age=1)
首先執行 deco(name='tom', age=1)
,返回 wrapper
。再接着執行 @wrapper
,至關於 Dog = wrapper(Dog)
。最後利用 setattr(obj, key, value)
爲類添加屬性。
描述符+裝飾器實現類型檢查
class Descriptor: def __init__(self, key, expected_type): self.key = key # 'name' self.expected_type = expected_type # str def __get__(self, instance, owner): # self:Descriptor對象, instance:People對象,owner:People return instance.__dict__[self,key] def __set__(self, instance, value): if isinstance(value, self.expected_type): instance.__dict__[self.key] = value else: raise TypeError('你傳入的%s不是%s' % (self.key, self.expected_type)) def deco(**kwargs): # kwargs:{'name': 'str', 'age': 'int'} def wrapper(obj): # obj:People for key, val in kwargs.items(): setattr(obj, key, Descriptor(key, val)) # key:name、age val:str、int return obj return wrapper @deco(name=str, age=int) # @wrapper ==> People = wrapper(People) class People: # name = Descriptor('name', str) def __init__(self, name, age, color): self.name = name self.age = age self.color = color p1 = People('rose', 18, 'white') print(p1.__dict__) p2 = People('rose', '18', 'white')
{'__module__': '__main__', '__init__': <function People.__init__ at 0x00000000051971E0>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Descriptor object at 0x00000000051BFDD8>, 'age': <__main__.Descriptor object at 0x00000000051D1518>} --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-18-010cd074c06d> in <module>() 30 print(People.__dict__) 31 ---> 32 p2 = People('rose', '18', 'white') <ipython-input-18-010cd074c06d> in __init__(self, name, age, color) 25 def __init__(self, name, age, color): 26 self.name = name ---> 27 self.age = age 28 self.color = color 29 p1 = People('rose', 18, 'white') <ipython-input-18-010cd074c06d> in __set__(self, instance, value) 11 instance.__dict__[self.key] = value 12 else: ---> 13 raise TypeError('你傳入的%s不是%s' % (self.key, self.expected_type)) 14 15 def deco(**kwargs): # kwargs:{'name': 'str', 'age': 'int'} TypeError: 你傳入的age不是<class 'int'>
在利用 setattr()
設置屬性的時候,對 value 進行類型檢查。
靜態屬性 property 可讓類或類對象,在調用類函數屬性時,相似於調用類數據屬性同樣。下面咱們利用描述符原理來模擬 property。
把類設置爲裝飾器,裝飾另外一個類的函數屬性:
class Foo: def __init__(self, func): self.func = func class Bar: @Foo # test = Foo(test) def test(self): pass b1 = Bar() print(b1.test)
<__main__.Foo object at 0x00000000051DFEB8>
調用 b1.test
返回的是 Foo 的實例對象。
利用描述符模擬 property
class Descriptor: # 1 """描述符""" def __init__(self, func): # 4 # func: area self.func = func # 5 def __get__(self, instance, owner): # 13 """ 調用 Room.area self: Descriptor 對象 instance: Room 對象(即 r1或Room中的 self) owner: Room 自己 """ res = self.func(instance) #14 # 實現調用 Room.area,area須要一個位置參數 self,即 r1 return res # 17 class Room: # 2 def __init__(self, name, width, height): # 7 self.name = name # 8 self.width = width # 9 self.height = height # 10 # area = Descriptor(area),Descriptor 對象賦值給 area,實際是給 Room 增長描述符 # 設置 Room 的屬性字典 @Descriptor # 3 def area(self): # 15 """計算面積""" return self.width * self.height # 16 r1 = Room('臥室', 3, 4) # 6 print(Room.__dict__) # 11 print(r1.area) # 12 調用 Descriptor.__get__()
{'__module__': '__main__', '__init__': <function Room.__init__ at 0x0000000005206598>, 'area': <__main__.Descriptor object at 0x000000000520C6A0>, 'test': <property object at 0x00000000051FFDB8>, '__dict__': <attribute '__dict__' of 'Room' objects>, '__weakref__': <attribute '__weakref__' of 'Room' objects>, '__doc__': None} 12
首先執行 @Descriptor
,至關於 area = Descriptor(area)
,相似於給類 Room 設置 area屬性。area 屬性又被 Descriptor 代理(描述)。
全部當執行 r1.area
的時候,觸發調用 Descriptor.__get__()
方法,而後執行 area() 函數
,並返回結果。
到目前爲止,模擬 property 已經完成了百分之八十。惟一不足的是:property 除了可使用實例對象調用外,還可使用類調用。只不過返回的是 property 這個對象:
class Room: ... @property def test(self): return 1 print(Room.test)
<property object at 0x000000000520FAE8>
那麼咱們也用類調用看看:
print(Room.area)
AttributeError: 'NoneType' object has no attribute 'width'
發現類沒有 width
這個屬性,這是由於咱們在使用 __get__()
方法時,其中 instance 參數接收的是類對象,而不是類自己。當使用類去調用時,instance = None
。
所以在使用模擬類調用時,須要判斷是否 instance 爲 None:
def __get__(self, instance, owner): if instance == None: return self ... print(Room.area)
<__main__.Descriptor object at 0x0000000005212128> ```` 發現返回的是 Descriptor 的對象,與 property 的返回結果同樣。到此爲止,咱們使用描述符成功地模擬 property 實現的功能。 **實現延遲計算功能** 要想實現延遲計算功能,只需每計算一次便緩存一次到實例屬性字典中便可: ```python def __get__(self, instance, owner): if instance == None: return self res = self.func(instance) setattr(instance, self.func.__name__, res) return res ... print(r1.area) # 首先在本身的屬性字典中找,本身屬性字典中有 area 屬性。由於已經緩存好上一次的結果,全部不須要每次都去計算
總結
@property、@staticmethod、@classmethod
,甚至是 __slots__
屬性。類方法 @classmethod
能夠實現類調用函數屬性。咱們也能夠描述符模擬出類方法實現的功能:
class ClassMethod: def __init__(self, func): self.func = func def __get__(self, instance, owner): def deco(*args, **kwargs): return self.func(owner, *args, **kwargs) return deco class Dog: name = 'tom' @ClassMethod # eat = ClassMethod(eat) 至關於爲 Dog 類設置 eat 類屬性,只不過它被 Classmethod 代理 def eat(cls, age): print('那隻叫%s的狗,今年%s歲,它正在吃東西' % (cls.name, age)) print(Dog.__dict__) Dog.eat(2) # Dog.eat:調用類屬性,觸發 __get__,返回 deco。再調用 deco(2)
{'__module__': '__main__', 'name': 'tom', 'eat': <__main__.ClassMethod object at 0x00000000052D1278>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None} 那隻叫tom的狗,今年2歲,它正在吃東西
類 ClassMethod 被定義爲一個描述符,@ClassMethod
至關於 eat = ClassMethod(eat)
。所以也至關於 Dog 類設置了類屬性 eat,只不過它被 ClassMethod 代理。
運行 Dog.eat(2)
,其中 Dog.eat
至關於調用 Dog 的類屬性 eat,觸發 __get__()
方法,返回 deco 。最後調用 deco(2)
。
靜態方法的做用是能夠在類中定義一個函數,該函數的參數與類以及類對象無關。下面咱們用描述符來模擬實現靜態方法:
class StaticMethod: def __init__(self, func): self.func = func def __get__(self, instance, owner): def deco(*args, **kwargs): return self.func(*args, **kwargs) return deco class Dog: @StaticMethod def eat(name, color): print('那隻叫%s的狗,顏色是%s的' % (name, color)) print(Dog.__dict__) Dog.eat('tom', 'black') d1 = Dog() d1.eat('lila', 'white') print(d1.__dict__)
{'__module__': '__main__', 'eat': <__main__.StaticMethod object at 0x00000000052EF940>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None} 那隻叫tom的狗,顏色是black的 那隻叫lila的狗,顏色是white的 {}
類以及類對象屬性字典中,皆沒有 name 和 color 參數,利用描述符模擬靜態方法成功。
靜態屬性 property 本質是實現了 get、set、delete 三個方法。
class A: @property def test(self): print('get運行') @test.setter def test(self, value): print('set運行') @test.deleter def test(self): print('delete運行') a1 = A() a1.test a1.test = 'a' del a1.test
get運行 set運行 delete運行
另外一種表現形式:
class A: def get_test(self): print('get運行') def set_test(self, value): print('set運行') def delete_test(self): print('delete運行') test = property(get_test, set_test, delete_test) a1 = A() a1.test a1.test = 'a' del a1.test
get運行 set運行 delete運行
應用
class Goods: def __init__(self): self.original_price = 100 self.discount = 0.8 @property def price(self): """實際價格 = 原價 * 折扣""" new_price = self.original_price * self.discount return new_price @price.setter def price(self, value): self.original_price = value @price.deleter def price(self): del self.original_price g1 = Goods() print(g1.price) # 獲取商品價格 g1.price = 200 # 修改原價 print(g1.price) del g1.price
80.0 160.0
Python 中一切皆對象,對象是由類實例化產生的。那麼類應該也有個類去產生它,利用 type()
函數咱們能夠去查看:
class A: pass a1 = A() print(type(a1)) print(type(A))
<class '__main__.A'> <class 'type'>
由上可知,a1 是類 A 的對象,而 A 是 type 類產生的對象。
當咱們使用 class 關鍵字的時候,Python 解釋器在加載 class 關鍵字的時候會自動建立一個對象(可是這個對象非類實例產生的對象)。
元類是類的類,也就是類的模板。用於控制建立類,正如類是建立對象的模板同樣。
在 Python 中,type 是一個內建的元類,它能夠用來控制生成類。Python 中任何由 class 關鍵字定義的類都 type 類實例化的對象。
使用 class 關鍵字定義:
class Foo: pass
使用 type()
函數定義:
type()
函數有三個參數:第一個爲類名(str 格式),第二個爲繼承的父類(tuple),第三個爲屬性(dict)。
x = 2 def __init__(self, name, age): self.name = name self.age = age def test(self): print('Hello %s' % self.name) Bar = type('Bar', (object,), {'x': 1, '__init__': __init__, 'test': test, 'test1': test1}) # 類屬性、函數屬性 print(Bar) print(Bar.__dict__) b1 = Bar('rose', 18) b1.test()
<class '__main__.Bar'> {'x': 1, '__init__': <function __init__ at 0x00000000055A6048>, 'test': <function test at 0x00000000055A60D0>, 'test1': <function test1 at 0x0000000005596BF8>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Bar' objects>, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__doc__': None} Hello rose
__init__ 與 __new__
構造方法包括建立對象和初始化對象,分爲兩步執行:先執行 __new__
,再執行 __init__
。
__new__
:在建立對象以前調用,它的任務是建立對象並返回該實例,所以它必需要有返回值,是一個靜態方法。__init__
:在建立對象完成後被調用,其功能是設置對象屬性的一些屬性值。也就是 __new__
建立的對象,傳給 __init__
做爲第一個參數(即 self)。
class Foo: def __init__(self): print('__init__方法') print(self) def __new__(cls): print('__new__方法') ret = object.__new__(cls) # 建立實例對象 print(ret) return ret f1 = Foo()
__new__方法 <__main__.Foo object at 0x00000000055AFF28> __init__方法 <__main__.Foo object at 0x00000000055AFF28>
總結
__new__
至少要有一個參數 cls,表明要實例化的類,由解釋器自動提供。必需要有返回值,能夠返回父類出來的實例,或直接 object
出來的實例。__init__
的第一個參數 self 就是 __new__
的返回值。若是一個類沒有指定元類,那麼它的元類就是 type,也能夠本身自定義一個元類。
class MyType(type): def __init__(self, a, b, c): # self:Bar a:Bar b:() 父類 c:屬性字典 print(self) print('元類 init 執行~') def __call__(self, *args, **kwargs): print('call 方法執行~') obj = object.__new__(self) # Bar 產生實例對象,即 b1 print(obj) self.__init__(obj, *args, **kwargs) # Bar.__init__(b1, name) return obj class Bar(metaclass=MyType): # Bar = MyType('Bar', (object,), {}) 觸發 MyType 的 __init__() def __init__(self, name): # self:f1 self.name = name b1 = Bar('rose') # Bar 也是對象,對象加括號,觸發 __call__() print(b1.__dict__)
<class '__main__.Bar'> 元類 init 執行~ call 方法執行~ <__main__.Bar object at 0x00000000055D3F28> {'name': 'rose'}
加載完程序後,首先執行 metaclass=MyType
,它至關於 Bar = MyType('Bar', (object,), {})
,它會執行 MyType 的 __init__
執行。
再接着執行 b1=Bar('rose)
,由於 Bar 也是對象,對象加括號就會觸發 __call__()
方法,再由 __new__()
方法產生實例對象,最後返回。
總結
__new__()
產生一個類的對象,並返回。