【转】Python-面向对象进阶
【轉】Python-面向對象進階
一、isinstance(obj, cls) and issubclass(sub, super)
1. isinstance(obj, cls),檢查obj是否是類cls的對象
1 class A: 2 pass 3 4 obj = A() 5 print(isinstance(obj, A)) 6 7 #運行結果 8 #True2. issubclass(sub, super),檢查sub類是否是super類的派生類(子類)
1 class A: 2 pass 3 4 class B(A): 5 pass 6 7 print(issubclass(B, A)) 8 9 #運行結果 10 #True二、反射
1. 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力(自省)。這一概念的提出很快引發了計算機科學領域關于應用反射性的研究。它首先被程序語言的設計領域所采用,并在Lisp和面向對象方面取得了成績。
2. python面向對象中的反射:通過字符串的形式操作對象相關的屬性。python中的一切事物都是對象(都可以使用反射)。
1 class People: 2 country = 'China' 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 def info(self): 8 print('%s is %d years old' % (self.name, self.age)) 9 10 p = People('jack', 18) 11 12 #hasattr(obj, name),檢查屬性 13 print(hasattr(p, 'info')) #檢查對象p是否有‘info’屬性,結果True 14 print(hasattr(p, 'name')) #檢查對象p是否有‘name’屬性,結果True 15 16 #getattr(obj, name)獲取屬性 17 print(getattr(p, 'name')) #獲得對象p的‘name’屬性,結果:返回p.name的值,即jack 18 print(getattr(p, 'info')) #獲得對象p的‘info’屬性,結果:返回p.info的值, 19 # 即綁定方法info的內存地址:<bound method People.info of <__main__.People object at 0x000001B64317ABE0>> 20 getattr(p, 'info')() #由于getattr返回的是對象方法屬性的內存地址,加()就可以調用,結果:jack is 18 years old 21 22 #setattr(x, y, v)設置屬性 23 setattr(p, 'age', 21) #修改對象p的‘age’屬性,結果:p.age的值變為21 24 setattr(p, 'sex', 'male') #新增對象p的‘sex’屬性,結果:p.sex的值為male 25 print(p.__dict__) #查看對象p的數據屬性,結果:{'name': 'jack', 'age': 21, 'sex': 'male'} 26 27 #delattr(x, y)刪除屬性 28 delattr(p, 'sex') #刪除對象p的‘sex’屬性 29 print(p.__dict__) #結果:{'name': 'jack', 'age': 21} 四個可以實現自省的函數:hasattr(obj, name);getattr(obj, name);setattr(x, y, v);delattr(x, y) 1 #類也是對象 2 class Foo(object): 3 staticField = "old boy" 4 5 def __init__(self): 6 self.name = 'wupeiqi' 7 8 def func(self): 9 return 'func' 10 11 @staticmethod 12 def bar(): 13 return 'bar' 14 15 16 print(getattr(Foo, 'staticField')) #獲取類的'staticField'屬性,結果:old boy 17 18 print(getattr(Foo, 'func')) #獲取類的'func'屬性,結果:<function Foo.func at 0x0000018156FBB950> 19 print(getattr(Foo, 'func')('self')) #加()調用方法,結果:func 20 21 print(getattr(Foo, 'bar')) #獲取類的'bar'屬性,結果:<function Foo.bar at 0x00000192D2AFB9D8> 22 print(getattr(Foo, 'bar')()) #加()調用方法,結果:bar 類也是對象,能夠應用反射 1 #反射當前模塊成員 2 #!/usr/bin/env python 3 # -*- coding:utf-8 -*- 4 5 import sys 6 7 8 def s1(): 9 print('s1') 10 11 12 def s2(): 13 print('s2') 14 15 16 this_module = sys.modules[__name__] 17 18 print(this_module) #結果:<module '__main__' from '......'> 19 print(hasattr(this_module, 's1')) #結果:True 20 print(getattr(this_module, 's2')) #結果:<function s2 at 0x0000020590EAB8C8> 21 getattr(this_module, 's2')() #結果:s2 模塊也是對象,能夠應用反射3. 反射的好處
好處一:實現可插拔機制
有倆程序員,一個lili,一個是egon,lili在寫程序的時候需要用到egon所寫的類,但是egon去跟女朋友度蜜月去了,還沒有完成他寫的類,lili想到了反射,使用了反射機制lili可以繼續完成自己的代碼,等egon度蜜月回來后再繼續完成類的定義并且去實現lili想要的功能。
總之反射的好處就是,可以事先定義好接口,接口只有在被完成后才會真正執行,這實現了即插即用,這其實是一種‘后期綁定’,什么意思?即你可以事先把主要的邏輯寫好(只定義接口),然后后期再去實現接口的功能。
1 class FtpClient: 2 'ftp客戶端,但是還么有實現具體的功能' 3 def __init__(self,addr): 4 print('正在連接服務器[%s]' %addr) 5 self.addr=addr 6 7 ############################## 8 #不影響lili的代碼編寫 9 10 from module import FtpClient 11 f1=FtpClient('192.168.1.1') 12 if hasattr(f1,'get'): 13 func_get=getattr(f1,'get') 14 func_get() 15 else: 16 print('---->不存在此方法') 17 print('處理其他的邏輯')好處二:動態導入模塊(基于反射當前模塊成員)
1 #兩種導入用戶輸入模塊得方法,官方推薦方法2 2 #方法1 3 m = input('input your module:') #用戶輸入要導入的模塊名,以time模塊為例 4 m1 = __import__(m) 5 print(m1) #結果:<module 'time' (built-in)> 6 print(m1.time()) #結果:1493023753.0157707,當前時間 7 8 #方法2 9 import importlib #先導入importlib模塊 10 t = importlib.import_module(m) 11 print(t) #結果:<module 'time' (built-in)> 12 print(t.time()) #結果:1493023753.0238242,當前時間三、內置attr
1 class Foo: 2 x = 1 3 def __init__(self, y): 4 self.y = y 5 6 def __getattr__(self, item): 7 print('----> from getattr:你找的屬性不存在') 8 9 def __setattr__(self, key, value): 10 print('----> from setattr') 11 # self.key=value #這就無限遞歸了 12 self.__dict__[key] = value #應該使用它 13 14 def __delattr__(self, item): 15 print('----> from delattr') 16 # del self.item #無限遞歸了 17 self.__dict__.pop(item) #應該使用它 18 19 #__setattr__添加/修改屬性會觸發它的執行 20 f1 = Foo(10) #因為重寫了__setattr__,凡是賦值操作都會觸發它的運行 21 print(f1.__dict__) #結果:----> from setattr {'y': 10} 22 f1.z = 3 #添加屬性 23 print(f1.__dict__) #結果:----> from setattr {'y': 10, 'z': 3} 24 25 #__delattr__刪除屬性的時候會觸發 26 f1.__dict__['a'] = 3 #我們可以直接修改屬性字典,來完成添加/修改屬性的操作 27 del f1.a #觸發__delattr__ 28 print(f1.__dict__) #結果:----> from delattr {'y': 10, 'z': 3} 29 30 #__getattr__只有在使用對象調用屬性且屬性不存在的時候才會觸發 31 print(f1.y) #屬性存在,結果:10 32 f1.a #屬性a不存在,觸發__getattr__,結果:----> from getattr:你找的屬性不存在四、二次加工標準類型(包裝)
包裝:python為大家提供了標準數據類型,以及豐富的內置方法,其實在很多場景下我們都需要基于標準數據類型來定制我們自己的數據類型,新增/改寫方法,這就用到了我們剛學的繼承/派生知識(其他的標準類型均可以通過下面的方式進行二次加工)
?
1 #二次加工標準類型(基于繼承實現) 2 class List(list): #繼承list所有的屬性,也可以派生出自己新的,比如append和mid 3 def append(self, p_object): 4 ' 派生自己的append:加上類型檢查' 5 if not isinstance(p_object, int): 6 raise TypeError('must be int') 7 super().append(p_object) 8 9 @property 10 def mid(self): 11 '新增自己的屬性' 12 index = len(self)//2 13 return self[index] 14 15 l = List([1, 2, 3, 4]) 16 print(l) 17 l.append(5) 18 print(l) #結果:[1, 2, 3, 4, 5] 19 # l.append('1111111') #報錯,必須為int類型 20 21 print(l.mid) #結果:3 22 23 #其余的方法都繼承list的 24 l.insert(0, -123) #插入元素 25 print(l) #結果:[-123, 1, 2, 3, 4, 5] 26 l.clear() #清空列表 27 print(l) #結果:[]授權:授權是包裝的一個特性,?包裝一個類型通常是對已存在的類型的一些定制,這種做法可以新建,修改或刪除原有產品的功能。其它的則保持原樣。授權的過程,即是所有更新的功能都是由新類的某部分來處理,但已存在的功能就授權給對象的默認屬性。
實現授權的關鍵點就是覆蓋__getattr__方法
1 # 授權示范 2 import time 3 4 5 class FileHandle: 6 def __init__(self, filename, mode='r', encoding='utf-8'): 7 self.file = open(filename, mode, encoding=encoding) #獲得文件句柄 8 9 def write(self, line): #重新定義write方法,新增添加時間的功能 10 t = time.strftime('%Y-%m-%d %T') 11 self.file.write('%s %s' % (t, line)) 12 13 def __getattr__(self, item): #文件操作的其它屬性在FileHandle類中找不到時,觸發__getattr__ 14 return getattr(self.file, item) 15 16 17 f1 = FileHandle('b.txt', 'w+') #新建文件b.txt,獲得文件句柄,賦給對象f1 18 f1.write('你好啊') #調用類中的定制方法write 19 f1.seek(0) #重置文件位置于文首,觸發__getattr__,正常調用 20 print(f1.read()) #打印文件內容,觸發__getattr__,正常調用,結果:2017-04-24 17:30:37 你好啊 21 f1.close() #關閉文件,觸發__getattr__,正常調用五、__getattribute__
__getattribute__是訪問屬性的方法,我們可以通過方法重寫來擴展方法的功能。
當獲取屬性時,直接return object.__getattribute__(self, *args, **kwargs)
如果需要獲取某個方法的返回值時,則需要在函數后面加上一個()即可。如果不加的話,返回的是函數引用地址。
1 class Foo: 2 def __init__(self, x): 3 self.x = x 4 self.y = 100 5 6 def __getattr__(self, item): #屬性不存在時觸發執行 7 print('getattr') 8 if item == 'y': 9 return 'y = 100' 10 else: 11 return "No %s attribute" %item 12 13 def __getattribute__(self, item): #屬性存不存在都會觸發執行,而且當與__getattr__同時存在時,僅執行自己,除非拋出錯誤后,會執行__getattr__ 14 print('__getattribute__ is called') 15 if item == 'x': 16 return 'x = %s' %(object.__getattribute__(self, item)) #返回屬性 17 else: 18 raise AttributeError("No 'x' attribute") #當拋出錯誤時,會去執行__getattr__ 19 20 21 f = Foo(10) 22 print(f.x) #object存在‘x’屬性,運行結果:__getattribute__ is called x = 10 23 print(f.y) #object存在‘y’屬性,觸發__getattribute__,不符合if條件,拋出錯誤,會去執行__getattr__ 24 #運行結果:__getattribute__ is called,getattr,y = 100 25 print(f.z) #object不存在‘z’屬性,但還會觸發_getattribute__,拋出錯誤,觸發__getattr__,返回return的值 26 #運行結果:__getattribute__ is called,getattr,No z attribute六、__setitem__,__getitem,__delitem__
觸發機制與attr一致,只是將對象操作屬性模擬為字典的格式:
1 # 把對象操作屬性模擬成字典的格式 2 class Foo: 3 def __init__(self, name): 4 self.name = name 5 6 def __getitem__(self, item): 7 return self.__dict__[item] 8 9 def __setitem__(self, key, value): 10 self.__dict__[key] = value 11 12 def __delitem__(self, key): 13 self.__dict__.pop(key) 14 15 16 f = Foo('egon') #實例化 17 18 print(f.name) #.的方式調用,運行結果:egon 19 print(f['name']) #字典方式調用,運行結果:egon 20 21 f.age1 = 18 22 f['age'] = 21 23 print(f.__dict__) #運行結果:{'name': 'egon', 'age1': 18, 'age': 21} 24 25 del f['age1'] 26 del f.age 27 print(f.__dict__) #運行結果:{'name': 'egon'} 把對象操作屬性模擬成字典的格式七、__str__,__repr__,__format__
改變對象的字符串顯示__str__,__repr__;自定制格式化字符串__format__
1 # _*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 format_dict = { 4 'nat': '{obj.name}-{obj.addr}-{obj.type}', # 學校名-學校地址-學校類型 5 'tna': '{obj.type}:{obj.name}:{obj.addr}', # 學校類型:學校名:學校地址 6 'tan': '{obj.type}/{obj.addr}/{obj.name}', # 學校類型/學校地址/學校名 7 } 8 9 10 class School: 11 def __init__(self, name, addr, type): 12 self.name = name 13 self.addr = addr 14 self.type = type 15 16 def __repr__(self): 17 return 'School(%s,%s)' % (self.name, self.addr) 18 19 def __str__(self): 20 return '(%s,%s)' % (self.name, self.addr) 21 22 def __format__(self, format_spec): 23 if not format_spec or format_spec not in format_dict: 24 format_spec = 'nat' 25 fmt = format_dict[format_spec] 26 return fmt.format(obj=self) 27 28 29 s1 = School('oldboy1', '北京', '私立') 30 print('from repr: ', repr(s1)) #運行結果:from repr: School(oldboy1,北京) 31 print('from str: ', str(s1)) #運行結果:from str: (oldboy1,北京) 32 print(s1) #默認以‘str’定義的方式輸出;運行結果:(oldboy1,北京) 33 34 ''' 35 str函數或者print函數--->obj.__str__() 36 repr或者交互式解釋器--->obj.__repr__() 37 如果__str__沒有被定義,那么就會使用__repr__來代替輸出 38 注意:這倆方法的返回值必須是字符串,否則拋出異常 39 ''' 40 print(format(s1, 'nat')) #指定格式‘nat’輸出;運行結果:oldboy1-北京-私立 41 print(format(s1, 'tna')) #指定格式‘tna’輸出;運行結果:私立:oldboy1:北京 42 print(format(s1, 'tan')) #指定格式‘tan’輸出;運行結果:私立/北京/oldboy1 43 print(format(s1, 'asfdasdffd')) #其它情況默認以‘nat’輸出;運行結果:oldboy1-北京-私立 自定義格式化輸出實例八、__slots__
正常情況下,當我們定義了一個class,創建了一個class的實例后,我們可以給該實例綁定任何屬性和方法,這就是動態語言的靈活性。
但是,如果我們想要限制實例的屬性怎么辦?比如,只允許對Student實例添加name和age屬性。為了達到限制的目的,Python允許在定義class的時候,定義一個特殊的__slots__變量,來限制該class實例能添加的屬性:
1 ''' 2 1.__slots__是什么:是一個類變量,變量值可以是列表,元祖,或者可迭代對象,也可以是一個字符串(意味著所有實例只有一個數據屬性) 3 2.引子:使用點來訪問屬性本質就是在訪問類或者對象的__dict__屬性字典(類的字典是共享的,而每個實例的是獨立的) 4 3.為何使用__slots__:字典會占用大量內存,如果你有一個屬性很少的類,但是有很多實例,為了節省內存可以使用__slots__取代實例的__dict__ 5 當你定義__slots__后,__slots__就會為實例使用一種更加緊湊的內部表示。實例通過一個很小的固定大小的數組來構建,而不是為每個實例定義一個 6 字典,這跟元組或列表很類似。在__slots__中列出的屬性名在內部被映射到這個數組的指定小標上。使用__slots__一個不好的地方就是我們不能再給 7 實例添加新的屬性了,只能使用在__slots__中定義的那些屬性名。 8 4.注意事項:__slots__的很多特性都依賴于普通的基于字典的實現。另外,定義了__slots__后的類不再 支持一些普通類特性了,比如多繼承。大多數情況下,你應該 9 只在那些經常被使用到 的用作數據結構的類上定義__slots__比如在程序中需要創建某個類的幾百萬個實例對象 。 10 關于__slots__的一個常見誤區是它可以作為一個封裝工具來防止用戶給實例增加新的屬性。盡管使用__slots__可以達到這樣的目的,但是這個并不是它的初衷。 更多的是用來作為一個內存優化工具。 11 12 ''' 13 14 class Student(object): 15 __slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱 16 17 s = Student() # 創建新的實例 18 s.name = 'jack' # 綁定屬性'name' 19 s.age = 21 # 綁定屬性'age' 20 #s.score = 99 # 綁定屬性'score';報錯:'Student' object has no attribute 'score' 21 22 #使用__slots__要注意,__slots__定義的屬性僅對當前類實例起作用,對繼承的子類是不起作用的: 23 class G(Student): 24 pass 25 26 g = G() # 創建新的實例 27 g.name = 'jack' # 綁定屬性'name' 28 g.age = 21 # 綁定屬性'age' 29 g.score = 99 # 綁定屬性'score' 限制實例的綁定屬性,當前類有效九、__next__和__iter__實現迭代器協議
如果一個類想被用于for ... in循環,類似list或tuple那樣,就必須實現一個__iter__()方法,該方法返回一個迭代對象,然后,Python的for循環就會不斷調用該迭代對象的__next__()方法拿到循環的下一個值,直到遇到StopIteration錯誤時退出循環。
我們以斐波那契數列為例,寫一個Fib類,可以作用于for循環:
1 class Fib(object): 2 def __init__(self): 3 self.a, self.b = 0, 1 # 初始化兩個計數器a,b 4 5 def __iter__(self): 6 return self # 實例本身就是迭代對象,故返回自己 7 8 def __next__(self): 9 self.a, self.b = self.b, self.a + self.b # 計算下一個值 10 if self.a > 100: # 退出循環的條件 11 raise StopIteration() 12 return self.a # 返回下一個值 13 14 for i in Fib(): 15 print(i) 斐波那契數列十、__doc__
返回對象的描述信息
class Foo:"""描述信息"""passprint(Foo.__doc__) #運行結果:描述信息#__doc__不能被繼承 class Bar(Foo):passprint(Bar.__doc__) #運行結果:None十一、__module__和__class__
__module__ 表示當前操作的對象在哪個模塊;__class__?表示當前操作的對象的類是什么
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 #將該文件存為 a.py 4 5 class C: 6 7 def __init__(self): 8 self.name = 'a.py' #與a.py同一目錄下創建新文件from a import C #從a模塊導入類C obj = C() print(obj.__module__) # 輸出 a,即:輸出模塊 print(obj.__class__) # 輸出 <class 'a.C'>,即:輸出類十二、__del__
析構方法,當對象在內存中被釋放時,自動觸發執行。
注:此方法一般無須定義,因為Python是一門高級語言,程序員在使用時無需關心內存的分配和釋放,因為此工作都是交給Python解釋器來執行,所以,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。
class Foo:def __del__(self):print('執行我啦')f1=Foo() del f1 #觸發__del__ print('------->')#輸出結果 執行我啦 ------->#########################class Foo:def __del__(self):print('執行我啦')f1=Foo() # del f1 print('------->')#輸出結果 -------> 執行我啦 #對于當前程序,由于print('------->')運行完后程序就結束了,在結束前,會自動觸發__del__十三、__enter__和__exit__
我們知道在操作文件對象的時候可以這么寫
1 with open('a.txt') as f: 2 '代碼塊'上述叫做上下文管理協議,即with語句,為了讓一個對象兼容with語句,必須在這個對象的類中聲明__enter__和__exit__方法
1 class Open: 2 def __init__(self, name): 3 self.name = name 4 5 def __enter__(self): 6 print('出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量') 7 # return self 8 9 def __exit__(self, exc_type, exc_val, exc_tb): 10 print('with中代碼塊執行完畢后執行__exit__') 11 12 13 with Open('a.txt') as f: 14 print('=====>執行代碼塊') 15 16 17 #運行結果: 18 """ 19 出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量 20 =====>執行代碼塊 21 with中代碼塊執行完畢后執行__exit__ 22 """ 上下文管理協議__exit__()中的三個參數分別代表異常類型,異常值和追溯信息,with語句中代碼塊出現異常,則with后的代碼都無法執行
1 class Open: 2 def __init__(self, name): 3 self.name = name 4 5 def __enter__(self): 6 print('出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量') 7 # return self 8 9 def __exit__(self, exc_type, exc_val, exc_tb): 10 print('with中代碼塊執行完畢后執行__exit__') 11 print(exc_type) 12 print(exc_val) 13 print(exc_tb) 14 #return True 15 16 with Open('a.txt') as f: 17 print('=====>執行代碼塊') 18 raise AttributeError('手動拋錯') 19 20 print('其它內容') #不會執行 21 22 23 #運行結果: 24 """ 25 出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量 26 raise AttributeError('手動拋錯') 27 =====>執行代碼塊 28 with中代碼塊執行完畢后執行__exit__ 29 <class 'AttributeError'> 30 手動拋錯 31 AttributeError: 手動拋錯 32 <traceback object at 0x0000027B163221C8> 33 """ with語句拋錯,后面語句不執行如果__exit()返回值為True,那么異常會被清空,就好像啥都沒發生一樣,with后的語句正常執行
1 class Open: 2 def __init__(self, name): 3 self.name = name 4 5 def __enter__(self): 6 print('出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量') 7 # return self 8 9 def __exit__(self, exc_type, exc_val, exc_tb): 10 print('with中代碼塊執行完畢后執行__exit__') 11 print(exc_type) 12 print(exc_val) 13 print(exc_tb) 14 return True 15 16 with Open('a.txt') as f: 17 print('=====>執行代碼塊') 18 raise AttributeError('手動拋錯') 19 20 print('其它內容') 21 #運行結果: 22 """ 23 出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量 24 =====>執行代碼塊 25 with中代碼塊執行完畢后執行__exit__ 26 <class 'AttributeError'> 27 手動拋錯 28 <traceback object at 0x000001CB500921C8> 29 其它內容 30 """ __exit__返回True,with后語句正常執行用途或者說好處:
十四、__call__
對象后面加括號,觸發執行。
注:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對于 __call__ 方法的執行是由對象后加括號觸發的,即:對象() 或者 類()()
1 class Foo: 2 def __init__(self): 3 pass 4 5 def __call__(self, *args, **kwargs): 6 print('__call__') 7 8 9 obj = Foo() # 執行 __init__ 10 obj() # 執行 __call__ 11 Foo()() # 執行 __call__ obj()觸發__call__十五、metaclass
1. 引子
1 class Foo: 2 pass 3 4 f1=Foo() #f1是通過Foo類實例化的對象python中一切皆是對象,類本身也是一個對象,當使用關鍵字class的時候,python解釋器在加載class的時候就會創建一個對象(這里的對象指的是類而非類的實例)
上例可以看出f1是由Foo這個類產生的對象,而Foo本身也是對象,那它又是由哪個類產生的呢?
1 #type函數可以查看類型,也可以用來查看對象的類,二者是一樣的 2 print(type(f1)) # 輸出:<class '__main__.Foo'> 表示,obj 對象由Foo類創建 3 print(type(Foo)) # 輸出:<class 'type'>2. 什么是元類?
元類是類的類,是類的模板
元類是用來控制如何創建類的,正如類是創建對象的模板一樣
元類的實例為類,正如類的實例為對象(f1對象是Foo類的一個實例,Foo類是 type 類的一個實例)
type是python的一個內建元類,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象
3. 創建類的兩種方式
方式一:
1 class Foo: 2 def func(self): 3 print('from func')方式二:
1 def func(self): 2 print('from func') 3 4 x=1 5 Foo=type('Foo',(object,),{'func':func,'x':1}) 6 7 # type(object_or_name, bases, dict)4. 自定義元類
一個類沒有聲明自己的元類,默認他的元類就是type,除了使用元類type,用戶也可以通過繼承type來自定義元類(順便我們也可以瞅一瞅元類如何控制類的創建,工作流程是什么)
?當創建一個元類的對象(也是類)時的流程:
1 class Mymeta(type): 2 def __init__(self, name, bases, dic): #5 執行__init__函數,初始化對象 3 print('===>Mymeta.__init__') #6 打印'===>Mymeta.__init__' 4 5 def __new__(cls, *args, **kwargs): #2 執行__new__函數,創建對象 6 print('===>Mymeta.__new__') #3 打印'===>Mymeta.__new__' 7 return type.__new__(cls, *args, **kwargs) #4 返回類Mymeta創建的對象Foo 8 9 class Foo(object,metaclass=Mymeta): #1 類Foo是元類Mymeta的對象,在創建對象時,會先后調用類的__new__和__init__方法 10 def __init__(self, name): 11 self.name = name 12 def __new__(cls, *args, **kwargs): 13 return object.__new__(cls) 14 15 16 17 #運行結果: 18 # ===>Mymeta.__new__ 19 # ===>Mymeta.__init__ 創建元類的對象的流程,只調用元類的new和init重點:當通過一個類創建一個對象的時候,會先后調用類的__new__和__init__方法。元類是用來創建類的,也就是說類是元類創建的對象,所以元類的__new__和__init__方法會被調用。
?當創建一個元類的子類的對象(實例)時的流程:
1 #元類 2 class MyType(type): 3 4 def __init__(self, class_name, bases=None, dict=None): #5 執行__init__函數,初始化對象 5 print('MyType init --->') #6 打印:'MyType init --->' 6 print(classmethod, type(class_name)) #7 打印:<class 'classmethod'> <class 'str'> 7 print(bases) #8 打印(Foo的bases是object):(<class 'object'>,); 8 print(dict) #9 打印:對象Foo的命名空間 9 10 11 def __new__(cls, *args, **kwargs): #2 執行__new__(MyType, *args, **kwargs)函數,創建對象; 12 print('===>Mymeta new') #3 打印:===>Mymeta new 13 return type.__new__(cls, *args, **kwargs) #4 返回類Mymeta創建的對象Foo 14 15 def __call__(self, *args, **kwargs): #11 執行__call__(Foo, *args, **kwargs) 16 print('MyType call --->', self, args, kwargs) #12 打印:MyType call ---> <class '__main__.Foo'> ('name',) {} 17 return type.__call__(self, *args, **kwargs) #13 返回由元類MyType創建好的對象Foo,此時,就要調用Foo的__new__和__init__ 18 19 20 class Foo(object, metaclass=MyType): #1 創建元類的對象Foo,觸發元類MyType的__new__和__init__ 21 x = 111 22 def __init__(self, name): #17 執行:__init__(Foo.obj, 'jack') 23 print('Foo init') #18 打印:Foo init 24 self.name = name #19 賦值:self.name = 'jack' 25 26 def __new__(cls, *args, **kwargs): #14 執行:__new__(cls, *args, **kwargs) 27 print('Foo new') #15 打印:Foo new 28 return object.__new__(cls) #16 返回由類Foo創建好的對象 29 30 f = Foo('jack') #10 到這一步,Foo對象已經創建好了,執行Foo('name'),相當于調用父類即MyType里的__call__方法,創建了Foo的對象,然后賦值給f 31 print(f.name) #20 打印:jack 32 33 #運行結果: 34 # ===>Mymeta new 35 # MyType init ---> 36 # <class 'classmethod'> <class 'str'> 37 # (<class 'object'>,) 38 # {'__module__': '__main__', '__qualname__': 'Foo', 'x': 111, '__init__': <function Foo.__init__ at 0x000001F945B748C8>, '__new__': <function Foo.__new__ at 0x000001F945B74950>} 39 # MyType call ---> <class '__main__.Foo'> ('jack',) {} 40 # Foo new 41 # Foo init 42 # jack 創建元類的對象的對象,對象名()相當于調用父類的call方法補充:
對于元類的查找,Python有一套規則:
?
?
?
參考資料:
1. http://www.cnblogs.com/linhaifeng/articles/6204014.html
2. http://bbs.csdn.net/topics/360135494
3. http://www.cnblogs.com/wilber2013/p/4695836.html
轉載于:https://www.cnblogs.com/langqi250/p/10070378.html
總結
以上是生活随笔為你收集整理的【转】Python-面向对象进阶的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 四款 App 上线个人年度使用报告,快来
- 下一篇: 嬢嬢在重庆什么意思