使用元類實現對物件的所有方法和屬性進行遍歷
之前嘗試跟著書上的教程寫爬蟲的IP代理池的時候,想要根據爬蟲抓取類(class Crawler)下的所有方法(主要是爬取各代理網站),當時是直接照著書上敲的,不太理解,今天重新看了一下元類的知識點,重新寫了一個實現該方法的模板,具體如下(元類的知識自行學習吧):
首先,我們先建立一個類(這是我們需要的類,以後用以建立物件):
class Foo(object):
a = 'a'
b = 'b'
c = 'c'
def aa(): print('aa')
def bb(): print('bb')
def cc(): print('cc')
那我們該如何遍歷所有的方法和屬性呢?這是就用到了元類,使用元類在py2和py3中有所不同: py2中:
class Foo(object):
__metaclass__ = upper_attr #設定Foo類的元類為MetaClass
# 書寫屬性及方法...
py3中:
class Foo(object, metaclass=MetaClass):
# 書寫屬性及方法...
此時,我們來建立MetaClass(先用函式解釋)用來處理我們Foo MetaClass接受三個引數,分別是:
- future_class_name: 你之前建立的類的名字(Foo,自動傳遞)
- future_class_parents: 你之前建立的類的父類(Object)
- future_class_attr: 你之前建立的類的屬性和方法(a, b, c, aa, bb, cc),這是一個字典列表,k: 變數名, v: 變數值,[{k,v},{k,v}…] PS: 說一下我個人理解: 在python中,無論是變數還是函式,其實都是一個變數指向一個地址,所以無論是屬性(普通變數)還是方法(函式),其實都是一個東西,只是看指向的記憶體地址到底是什麼而已。也就是說,無論是屬性還是方法,傳遞的時候都一視同仁,傳遞的時候其實就是一個指標而已,也就是[{p1, *p1},{p2, *p2}…]。 證明見文末。
def MetaClass(future_class_name, future_class_parents, future_class_attr):
那麼,按照我的PS部分理解,屬性和函式其實都是一樣的,還都是字典,那我們就用字典Attrs表示吧(這個根據個人吧,我沒查什麼資料,所以不知道別人都怎麼表示的),同時,我們來給屬性和方法區分開(用不同字典儲存):
attrs["Func"] = {}
attrs["Attr"] = {}
由於我們知道,future_class_attrs是Foo的所有屬性和方法(字典儲存),因此,我們可以遍歷字典來讀取Foo的屬性和方法(我在py3中測試發現,屬性中額外多了兩個:__module__和__qualname__,這兩個我沒深究,不是Foo中自帶的,我們就不管它了,因此if判斷處理一下(如果需要,自行修改)):
for k, v in future_class_attrs.items():
# print(k,v,type(k),type(v))
# 這個不是使用者定義的屬性,是內部屬性
if k == '__module__' or k == '__qualname__':
continue
if isinstance(v, type(lambda _: _)): # 判斷是否是方法
# if k.startswith("Crawl_"): # 對構造的方法處理
attrs["Func"][k] = v
else:
# if k.startswith("Crawl_"): # 對構造的方法處理
attrs["Attr"][k] = v
因為我們想把方法(其實是函式)和屬性分離,由於我不知道函式的型別怎麼寫,所以就直接type(lambda: _: _))來表示一個函式型別的,這樣呼叫isinstance來判斷v(變數所指記憶體)是不是函式,如果是函式,我們則將 字典attrs[“Func”]的k賦值為v,對於屬性也一樣處理。這樣我們就實現了屬性和方法的分離。如果我們需要對其中的屬性和方法進行處理,則可在此進行,比如之前所說的,我想執行Foo下來的所有方法名以”Crawl_“開頭方法,那麼我們則可以使用if k.startswith(“Crawl_”)來進行處理,然後用相同方法,可以單獨儲存到一個arrts的一個子項中,這裡不作贅述。 好了,屬性和方法處理完了,正面就應該建立類了(下面我們用三種方法來建立):
# 方法1:通過'type'來做類物件的建立
return type(future_class_name, future_class_parents, attrs)
# 方法2:複用type.__new__方法, 這就是基本的OOP程式設計,沒什麼魔法
# return type.__new__(cls, future_class_name, future_class_parents, attrs)
# 方法3:使用super方法
# return super(UpperAttrMetaClass, cls).__new__(cls, future_class_name,
# future_class_parents, attrs)
到此,我們就完成了對Foo的處理,那我們應該怎麼遍歷Foo的屬性和方法呢?注意,在上面,我們將方法和屬性分別放在了字典Func和Attr中,這個不好說,我來將MetaClass的第三引數合併並解開展示一下(假設只有屬性a和方法aa): 對於Foo中的:
a = "a"
def aa()
在引數中和體現其實就是這樣的:
future_class_attrs = [{"a": "a"}, {"aa": aa}]
然後我們將future_class_attrs進行了拆分,形成了:
attrs = {
"Attr": {"a": "a"}
"Func":{"aa": aa}
}
然後我們建立了類,這時,真正的Foo就被處理成了:
"Attr": {"a": "a"}
"Func":{"aa": aa}
其實,Foo中就只有兩個屬性,其真正的屬性和方法分別在Foo屬性中儲存(這就比較像lua或js中物件的處理方法了(具體見用Python的func和dict模擬js或lua的面向物件 )) 因此我們在遍歷Foo的屬性和方法的時候可以這樣:
print("-----列印屬性-----")
for attr in f.Attr.keys():
print(f.Attr[attr])
print("\n-----列印方法-----")
for func in f.Func.keys():
f.Func[func]()
實現下來的結果是:
-----列印屬性-----
a
b
c
-----列印方法-----
aa
bb
cc
當然,對Func和Attr的儲存結構,也可以使用list,具體如下,不作過多解釋:
attrs = {}
attrs["FuncCount"] = 0
attrs["AttrCount"] = 0
attrs["Func"] = []
attrs["Attr"] = []
for k, v in future_class_attrs.items():
# 這個不是使用者定義的屬性,是內部屬性
if k == '__module__' or k == '__qualname__':
continue
if isinstance(v, type(lambda _: _)): # 判斷是否是方法
attrs["FuncCount"] += 1
attrs["Func"].append([k, v])
else:
attrs["AttrCount"] += 1
attrs["Attr"].append([k, v])
其遍歷方法為(attr或func為[name, value]的列表,name為變數名,value為變數所指記憶體地址):
print("-----列印屬性-----")
for i in range(f.AttrCount):
attr = f.Attr[i]
# print(attr[0], attr[1])
print(attr[1])
print("\n-----列印方法-----")
for i in range(f.FuncCount):
func = f.Func[i]
# print(func[0], func[1])
func[1]()
附上完整版程式碼(字典儲存版,列表儲存的就不放了,我用的是py3):
#-*- coding:utf-8 -*-
def MetaClass(future_class_name, future_class_parents, future_class_attrs):
attrs = {}
attrs["Func"] = {}
attrs["Attr"] = {}
for k, v in future_class_attrs.items():
# print(k,v,type(k),type(v))
# 這個不是使用者定義的屬性,是內部屬性
if k == '__module__' or k == '__qualname__':
continue
if isinstance(v, type(lambda _: _)): # 判斷是否是方法
# if k.startswith("Crawl_"): # 對構造的方法處理
attrs["Func"][k] = v
else:
# if k.startswith("Crawl_"): # 對構造的方法處理
attrs["Attr"][k] = v
# 方法1:通過'type'來做類物件的建立
return type(future_class_name, future_class_parents, attrs)
# 方法2:複用type.__new__方法, 這就是基本的OOP程式設計,沒什麼魔法
# return type.__new__(cls, future_class_name, future_class_parents, attrs)
# 方法3:使用super方法
# return super(UpperAttrMetaClass, cls).__new__(cls, future_class_name,
# future_class_parents, attrs)
class Foo(object, metaclass=MetaClass):
a = 'a'
b = 'b'
c = 'c'
def aa(): print('aa')
def bb(): print('bb')
def cc(): print('cc')
f = Foo()
print("-----列印屬性-----")
for attr in f.Attr.keys():
print(f.Attr[attr])
print("\n-----列印方法-----")
for func in f.Func.keys():
# print(f.Func[func])
f.Func[func]()
上邊,我們把主要內容講完了,下面,我們來用真正的class來當作元類:
#-*- coding:utf-8 -*-
class MetaClass(type):
# __new__ 是在__init__之前被呼叫的特殊方法
# __new__是用來建立物件並返回之的方法
# 而__init__只是用來將傳入的引數初始化給物件
# 你很少用到__new__,除非你希望能夠控制物件的建立
# 這裡,建立的物件是類,我們希望能夠自定義它,所以我們這裡改寫__new__
# 如果你希望的話,你也可以在__init__中做些事情
# 還有一些高階的用法會涉及到改寫__call__特殊方法,但是我們這裡不用
def __new__(cls, future_class_name, future_class_parents, future_class_attrs):
attrs = {}
attrs["Func"] = {}
attrs["Attr"] = {}
for k, v in future_class_attrs.items():
# 這個不是使用者定義的屬性,是內部屬性
if k == '__module__' or k == '__qualname__':
continue
if isinstance(v, type(lambda _: _)): # 判斷是否是方法
# if k.startswith("Crawl_"): # 對構造的方法處理
attrs["Func"][k] = v
else:
# if k.startswith("Crawl_"): # 對構造的方法處理
attrs["Attr"][k] = v
# 方法1:通過'type'來做類物件的建立
return type(future_class_name, future_class_parents, attrs)
# 方法2:複用type.__new__方法, 這就是基本的OOP程式設計,沒什麼魔法
# return type.__new__(cls, future_class_name, future_class_parents, attrs)
# 方法3:使用super方法
# return super(UpperAttrMetaClass, cls).__new__(cls, future_class_name,
# future_class_parents, attrs)
class Foo(object, metaclass=MetaClass):
a = 'a'
b = 'b'
c = 'c'
def aa(): print('aa')
def bb(): print('bb')
def cc(): print('cc')
f = Foo()
print("-----列印屬性-----")
for attr in f.Attr.keys():
print(f.Attr[attr])
print("\n-----列印方法-----")
for func in f.Func.keys():
# print(f.Func[func])
f.Func[func]()
好了,下面我證明一下,上邊說的屬性和方法是同樣的東西:
def func():
print("inner func ==>\t",'func')
func()
f = func
print("print(func) ==>\t", func)
func = 1
print("print(func) ==>\t", func)
f()
執行結果為:
inner func ==> func
print(func) ==> <function func at 0x0000000001D1C378>
print(func) ==> 1
inner func ==> func
上邊可能會有不少錯誤,請各位指正,謝謝!