Python基础教程:类的特殊成员及高级特性
1 對象的格式化輸出
(1)如果需要對一個對象(實例)進行格式化輸出,可以重寫類的__repr__()和__str__()方法。
兩者的區別:使用交互式解釋器輸出對象時,結果是__repr__()方法返回的字符串;使用 str() 或 print() 函數會輸出__str__()方法返回的字符串。
參見下例:
class Point:"""二維坐標系中的點"""def __init__(self, x, y):self.x = xself.y = ydef __repr__(self):return "Point({0.x!r}, {0.y!r})".format(self)def __str__(self):return "({0.x!s}, {0.y!s})".format(self)在交互式命令行【ipython】中的結果:
In [2]: point = Point(1, 2)In [3]: point Out[3]: Point(1, 2)In [4]: print(point) (1, 2)可以看到,在交互式環境中格式化輸出對象是Point(1, 2);而通過print()打印出的對象是(1, 2)。
(2) 注意,在格式化中使用 !r 表示輸出使用 __repr__()來代替默認的__str__()。
In [5]: print("point is {!r}".format(point)) point is Point(1, 2)In [6]: print("point is {!s}".format(point)) point is (1, 2)In [7]: print("point is {}".format(point)) point is (1, 2)如果__str__()沒有被定義,會使用__repr__()來代替輸出。通常來講自定義__repr__()和 __str__()是很好的習慣,因為它能簡化調試和實例輸出。
2 獲取類的描述
可以通過__doc__這個特殊字段獲取類的描述【即類的注釋】,用法 類名.__doc__,參見下列示例:
''' 學習中遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' class A:"""description..."""def func(self):passprint(A.__doc__) # description...3 獲取類或對象的所有成員
可以通過__dict__獲取到類或對象的所有成員信息(字典形式),用法 類名.__dict__或者對象.__dict__,參見下例:
class A:def __init__(self, name):self.name = namedef func(self):pass# 獲取類的所有成員 print(A.__dict__) #{'__module__': '__main__', 'func': <function A.func at 0x00000000025976A8>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}# 獲取對象的所有成員 obj = A("Liu You Yuan") print(obj.__dict__) # {'name': 'Liu You Yuan'}可以看到類與對象成員之中,只有【普通字段】是存儲在對象中的,其他成員都是在類中。
4 獲取創建當前操作的對象的類名
通過__class__能夠獲取當前操作的對象是由哪個類所創建,用法對象.__class__,參見下例:
''' 學習中遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' class A:def func(self):passobj = A()# 獲取 [當前操作的對象] 所在的類名 print(obj.__class__) # <class '__main__.A'>5 獲取創建當前操作的對象的類所在的模塊名
通過__module__能夠獲取創建當前操作的對象的類所在的模塊,用法對象.__module__,參見下例:
class A:def func(self):passobj = A() # 獲取 [當前操作的對象] 所在的模塊名 print(obj.__module__) # __main__6 讓對象可迭代
只需要在類中實現__iter__()方法,即可讓對象作用于for循環。參見如下例子:
class A:def __init__(self, lis):self.lis = lisdef __iter__(self):return iter(self.lis)obj = A([2018, 0, 3, 1, 9]) for i in obj: # 當對象用于迭代時,實際是相當于迭代__iter__方法的返回值。print(i)# 2018 # 0 # 3 # 1 # 9上例中,如果沒有實現__iter__()方法,對象obj是不能被循環的。實際上,像list、dict、str等數據結構之所能夠被迭代,就是其類中實現了__iter__()方法。
7 讓對象支持字典操作
實現__getitem__ / __setitem__ / __delitem__, 可實現對象類似字典的操作,參見下例:
''' 學習中遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' class Person:def __init__(self, name):self.name = namedef __getitem__(self, k):return self.namedef __setitem__(self, k, v):self.name = vdef __delitem__(self, k):del self.nameobj = Person("Jeo Chen")result = obj['name'] # 自動觸發執行 __getitem__ print(result) # Jeo Chenobj['name'] = 'Liu You Yuan' # 自動觸發執行 __setitem__ print(obj['name']) # Liu You Yuandel obj['name'] # 自動觸發執行 __delitem__8 優化大量對象占用的內存
如果需要創建大量(成千上萬)的對象,導致很占內存,可以通過特殊的靜態字段__solts__來減少對象所占用的內存。
下例將對比定義__solts__和 沒有定義__solts__的兩個類在創建大量對象時占用的內存大小,其中用了【反射的知識】和 【tracemalloc包】。
tracemalloc包是跟蹤由Python分配的內存塊的調試工具。其中:
(1)tracemalloc.start()方法表示開始跟蹤Python內存分配,開始時內存占用設為1;tracemalloc.stop()表示停止跟蹤;
(2)tracemalloc.get_traced_memory()方法能獲取由 tracemalloc 模塊跟蹤的內存塊的當前大小和峰值大小作為元組:(current: int, peak: int),單位為字節。
詳細參見下例:
import tracemallocITEM_NUM = 10 class HaveSlots:__slots__ = ['item%s' % i for i in range(ITEM_NUM)]def __init__(self):for i in range(len(self.__slots__)):setattr(self, 'item%s' % i, i)class NoSlots:def __init__(self):for i in range(ITEM_NUM):setattr(self, 'item%s' % i, i)# 開始跟蹤 tracemalloc.start()obj = [NoSlots() for i in range(100)] # 獲取由 tracemalloc 模塊跟蹤的內存塊的當前大小和峰值大小作為元組:(current: int, peak: int) print(tracemalloc.get_traced_memory())# 停止跟蹤 tracemalloc.stop()# 又開始跟蹤,相當于重置 tracemalloc.start() obj2 = [HaveSlots() for i in range(100)] print(tracemalloc.get_traced_memory())# (21832, 22219) # 未定義__slots__字段,創建100個對象占用的內存約為 21832 字節 # (13760, 14147) # 定義__slots__字段,創建100個對象占用的內存約為 13760 字節上例可見,當定義了__slots__字段時, 創建大量對象所占用的內存(13760) 明顯小于 沒有定義__slots__字段的內存(21832)。或許,你得到的占用內存大小與我得到的不一致,但不影響最終結論。
__slots__究竟做了什么來降低內存呢?
(1)默認情況下,自定義的對象都使用dict來存儲屬性(通過obj.__dict__查看),而python中的dict的底層需要考慮“降低hash沖突”,因此dict所占存儲空間要比實際存儲的元素大,會浪費一定的空間。
(2)使用__slots__后的類所創建的對象只會用到這些_slots__定義的字段,也就是說,每個對象都是通過一個很小的固定大小的數組來構建字段,而不是字典。
需要注意的是:
(1)如果聲明了__slots__,那么對象就不會再有__dict__屬性。
(2)使用__slots__意味著不能再給實例添加新的屬性,只能使用在__slots__中定義的那些屬性名。
(3)定義了__slots__后的類不再支持一些普通類特性了,比如多繼承。
因此,如果需要創建成千上萬的對象,__slots__比較適用;其他情況,還是要減少對__slots__使用的沖動。
9 構造方法
類中的__init__()方法就是類的構造方法了,通過類創建對象時,自動觸發執行。參見下例:
''' 學習中遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' class A:def __init__(self, name):self.name = nameprint("this is __init__")# 創建對象則自動觸發__ini__方法。 obj = A("Jeo Chen") # this is __init__直到此時,才介紹構造方法其實是為后面的內容鋪墊。這里說創建對象時自動觸發執行構造方法是不準確的,繼續往下讀,會介紹__init__的真正作用。
10 析構方法
__del__方法即為類的析構方法,當對象在內存中被釋放時,自動觸發執行。不過,Python是有垃圾回收機制的高級語言,我們無需關心內存的分配和釋放。解釋器在進行垃圾回收時自動觸發執行的析構方法。
class A:def __del__(self):pass11 __call__方法
當在對象后面加括號,即 【對象()】會自動觸發__call__方法,這一點要與構造方法相區別。構造方法是類名后面加括號,即【類名()】觸發。
class A:def __call__(self):print("this __call__")obj = A() obj() # 對象后面加括號,觸發__call__ # this __call__12 __new__方法
實際上,在創建對象時,調用__init__方法之前,就調用了__new__方法。我們可以通過下例證明:
''' 學習中遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' class A:def __init__(self, name):print("In A init")self.name = namedef __new__(cls, *args, **kwargs):print("In A new",)return object.__new__(cls)# 創對象 obj = A("Liu You Yuan") # In A new # In A init那么__new__方法到底有什么作用?下例演示了不調用__init__方法創建一個對象,詳見如下:
class Person:def __init__(self, name):print("in Person init")self.name = namedef __new__(cls, *args, **kwargs):print("In Person new",)return object.__new__(cls)# 不調用 __init__() 方法來創建Person對象 obj = Person.__new__(Person) print(obj) print(obj.name)執行結果如下:
In Person new <__main__.Person object at 0x00000000025E8EB8> Traceback (most recent call last):File "D:/githubfile/pythonclub/面向對象/new.py", line 34, in <module>print(obj.name) AttributeError: 'Person' object has no attribute 'name'分析輸出結果:
(1)第一行打印了 “In Person new” 說明確實是調用了__new__方法;另外并沒有打印 “in Person init” 說明確實沒有調用__init__方法。
(2)第二行打印了<__main__.Person object at 0x00000000025E8EB8>, 說明確實創建了一個對象。
(3)接著有報錯,報錯內容說AttributeError: 'Person' object has no attribute 'name', 對象并沒有name屬性(字段)。
綜上,上例的結果不言而喻:
(1)__new__方法才是真正創建對象的,只不過它創建的對象在沒調用__init__前是沒有經過【初始化】的。
(2)__init__方法是初始化對象的,【初始化】的過程也就是將字段封裝到對象中,通過 對象.字段 就能訪問。
13 類是怎么產生的
常常聽到,“python 一切皆對象”, 如此,“類” 本身也是對象,既然是對象,必然有創建它的類。換言之,"類"這個對象,是由"某個特殊的類"實例化而來。這個特殊的類就是type(),又稱元類。
除了之前介紹的通過class關鍵字可以定義類,通過type()也能定義,參見下例:
這樣證實了通過元類type()也能定義一個類,而且跟用class關鍵字定義的效果一樣,只不過不常用這種方式罷了。
type()和我們平常創建類和對象有什么關系呢?我們可以通過下例一探究竟:
在上例代碼中,值得注意的是:
(1)MyType繼承了type類,同時自定義了__init__ / __new__ / __call__方法。
(2)Person類中有個參數metaclass,用來指定創建Person的類, 也就是metaclass指定了由MyType這個類通過實例化,創建Person這個對象。
(3)這里多說一句,于我們而言,Person是我們定義的一個類;于MyType而言,Person是MyType創建的一個對象。
上例輸出結果如下:
In MyTyPe new In MyType init這可以看出我們只是定義了兩個類,做了一些自定義修改。當運行上述代碼時,就已經調用了MyType類的__new__和__init__方法了,也就是這時候通過MyType已經創建好了Person這個對象了。
接著我們在上例中添加一行,再運行:
obj = Person("Liu You Yuan")運行結果如下:
In MyTyPe new In MyType init In MyType call In Person new In Person init分析:
(1)當執行代碼obj = Person(“Liu You Yuan”), 我們把Person看成是MyType創建的對象,那么此行代碼就是在Person對象后面加了括號,這就會觸發MyType類中的__call__方法,打印“In MyType call”;
(2)此時,__call__中的self就是Person這個對象,通過self.__new__ / self.__init__主動調用了Person中的__new__ 和__init__,這也就創建出了obj這個對象。
至此,我們知道,當我們創建一個類A,并通過類A創建對象obj時,實際上是經歷了兩個過程:
(1)通過元類type創建我們定義的類A。
(2)通過類A創建對象obj。
總結
以上是生活随笔為你收集整理的Python基础教程:类的特殊成员及高级特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Python基础教程】for循环用法详
- 下一篇: Python中必须知道的知识点:上下文管