1. 程式人生 > >使用元類實現對物件的所有方法和屬性進行遍歷

使用元類實現對物件的所有方法和屬性進行遍歷

之前嘗試跟著書上的教程寫爬蟲的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接受三個引數,分別是:

  1. future_class_name: 你之前建立的類的名字(Foo,自動傳遞)
  2. future_class_parents: 你之前建立的類的父類(Object)
  3. 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

上邊可能會有不少錯誤,請各位指正,謝謝!