1. 程式人生 > >理解python中的元類

理解python中的元類

一,理解類也是物件

在python中類同樣也是一種物件,只要使用關鍵字class,Python直譯器在執行的時候就會建立一個物件,這個物件(類)自身擁有建立物件(類例項)的能力,這就是為什麼他是一個類的原因,但是,他的本質任然是一個物件。

class objectCreator(object):
    pass


my_object = objectCreator()
print(my_object)  # 可以列印一個類,因為他其實就是一個物件


def echo(obj):  # 可以將類作為引數傳給函式
    print(obj)


echo(objectCreator)

objectCreator.new_attribute 
= "foo" # 可以增加屬性 print(hasattr(objectCreator, "new_attribute")) objectCreatorMirror = objectCreator # 可以賦值給你個變數 print(objectCreatorMirror())

二,動態地建立類

1,通過return class動態的構建需要的類

因為類也是物件,你可以在執行時動態的建立他們,就像他們任何物件一樣,首先,你可以在函式中建立類,使用class關鍵字即可。

def choose_class(name):
    if name == "foo":
        
class Foo(object): pass return Foo else: class Bar(object): pass return Bar MyClass = choose_class("foo") print(MyClass) # 返回是類,不是類的例項 print(MyClass()) # 類的建立

2,通過type函式構造類,重新認識不一樣的type

type可以動態建立類。type可以接受一個類的描述作為引數, 然後返回一個類。

type的語法:

type(類名,父類的元祖(針對繼承的情況,可以為空), 包含屬性的字典(名稱和值))

如下程式碼:

class MyShinClass(object):
    pass

可以手動通過type建立,其實

MyShinyClass = type("MyShinyClass", (), {})  # 返回一個類物件
print(MyShinyClass)
print(MyShinClass())

type建立類的範例:

1, 構建Foo類
class Foo(object):
    bar = True
    
Foo = type("Foo", (), {"bar": True})

2, 繼承Foo類
class FooChild(Foo):
    pass


FooChild = type("FooChild", (Foo,), {})

# 3, 為FooChild類增加方法
def echo_bar(self):
    print(self.bar)


FooChild = type("FooChild", (Foo,), {"echo_bar": echo_bar})
print(hasattr(FooChild, "echo_bar"))

my_foo = FooChild()
my_foo.echo_bar()

可以看到,在python中,類也是物件,你可以動態的建立類。這就是當我們使用關鍵字class時python在幕後做的事情,而這就是通過元類實現的

三,元類

1,什麼時元類

通過上文的描述,可以得到python中的類也是物件,元類就是用來建立這些類(物件)的。元類就是類的類。

函式type實際上就是一個元類,type就是python在背後用來建立所有類的元類。

元類就是建立類這種物件的東西。type就是python的內建元類,當然了,你也可以建立自己的元類

2, __metaclass__屬性

可以在寫一個類的時候為其新增__metaclass__屬性,定義了__metaclass__就定義了這個類的元類

class Foo(object):
    __metaclass__ = something

class Foo(metaclass=something):
    __metaclass__ = something

例如:如下程式碼

class Foo(Bar):
    pass

在該類並定義的時候,他還沒有在記憶體中生成,直到他被呼叫,Python做了如下的操作:

1)Foo中有__mataclass__這個屬性嗎?如果有,python會在記憶體中通過__metaclass__建立一個名字為Foo的類物件

2)如果Python沒有找到__metaclass__,他就會在父類中尋找__metaclass__屬性,並嘗試做同樣的操作。

3)如果Python沒有找到__metaclass__,他就會在模組層中尋找__metaclass__,並嘗試做同樣的操作。

4)如果還是找不到__metaclass__,Python就會用內建的type來建立這個類物件

三,自定義元類

元類的主要目的就是當建立類的時候能自動地改變類,通常,你會為API做這樣的事情, 你希望可以建立符合上下文的類。假象一個你的模組裡所有累的屬性都應該是大寫形式,有好幾種方法可以辦到,但其中一種就是通過設定__metaclass__。採用這種方法,這個模組中所有的類都會通過這個元類建立。我們只需要告訴元類吧所有的屬性都改成大寫形式就萬事大吉

class UpperAttrMetaClass(type):
    def __new__(cls, name, bases, dic):
        # 獲取傳入的屬性值
        attrs = ((name, value) for name, value in dic.items() if not name.startswith("__"))
        # 將屬性值變成大寫,轉換成字典
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        
        return type.__new__(cls, name, bases, uppercase_attr)


# python3之後必須這樣寫
class Foo(metaclass=UpperAttrMetaClass):
    bar = "bip"


f = Foo()
print(hasattr(Foo, 'bar'))  # False
print(hasattr(Foo, 'BAR'))  # True

2,使用class來當作元類

由於__metaclass__必須返回一個類

四,使用__new__方法和元類方式分別實現單例模式

class Singleton(type):
    def __init__(self, *args, **kwargs):
        print("__init__")
        self._instance = None
        super(Singleton, self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("__call__")
        if self._instance is None:
            self._instance = super(Singleton, self).__call__(*args, **kwargs)
        return self._instance


class Foo(metaclass=Singleton):
    pass


foo1 = Foo()
foo2 = Foo()

print(Foo.__dict__)
print(foo1 is foo2)

# __init__
# __call__
# __call__

# {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>,
#  '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None,
# '_instance': <__main__.Foo object at 0x102a034e0>}

# True

基於這個例子:

我們知道元類生成的例項是一個類,而這裡我們僅僅需要對這個例項增加一個屬性來判斷和儲存生成的單例。

關於__call__方法的呼叫,因為Foo是Singleton的一個例項,所以Foo()這樣的方式就呼叫了Singleton的__call__方法。

實現:

定義一個元類方法,實現函式方法的擴充套件自動識別,加入類方中中

class MetaClassTest(type):
    def __new__(cls, name, bases, attrs):
        count = 0
        attrs["__FuncTest__"] = []
        for k, v in attrs.items():
            if "Chen_" in k:
                attrs["__FuncTest__"].append(k)
                count += 1
        attrs["__FuncCount__"] = count

        return type.__new__(cls, name, bases, attrs)


class MainTest(object, metaclass=MetaClassTest):

    def get_data(self, callback):
        if hasattr(self, callback):
            result = getattr(self, callback)()
            print(result, "執行完畢~~~")

    # 以後只要每次擴充套件Chen_開頭的函式,就會自動執行。
    def Chen_Run(self):
        return "Chenrun"

    def Chen_Peng(self):
        return "ChenPeng"


def run():
    obj = MainTest()
    for func in range(obj.__FuncCount__):
        obj.get_data(callback=obj.__FuncTest__[func])


if __name__ == '__main__':
    run()