這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)python中metaclass的作用是什么,文章內(nèi)容豐富且以專(zhuān)業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
創(chuàng)新互聯(lián)是一家專(zhuān)注網(wǎng)站建設(shè)、網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃、微信平臺(tái)小程序開(kāi)發(fā)、電子商務(wù)建設(shè)、網(wǎng)絡(luò)推廣、移動(dòng)互聯(lián)開(kāi)發(fā)、研究、服務(wù)為一體的技術(shù)型公司。公司成立10多年以來(lái),已經(jīng)為上千三輪攪拌車(chē)各業(yè)的企業(yè)公司提供互聯(lián)網(wǎng)服務(wù)?,F(xiàn)在,服務(wù)的上千客戶(hù)與我們一路同行,見(jiàn)證我們的成長(zhǎng);未來(lái),我們一起分享成功的喜悅。
類(lèi)也是對(duì)象
在理解metaclass之前,我們先要掌握python中的類(lèi)(class)是什么。
python中類(lèi)的概念,是借鑒自smalltalk語(yǔ)言。
在大部分語(yǔ)言中,類(lèi)指的是"描述如何產(chǎn)生一個(gè)對(duì)象(object)"的一段代碼,這對(duì)于python也是如此。
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
但是,在python中,類(lèi)遠(yuǎn)不止如此,類(lèi)同時(shí)也是對(duì)象。當(dāng)你遇到關(guān)鍵詞class的時(shí)候,python就會(huì)自動(dòng)執(zhí)行產(chǎn)生一個(gè)對(duì)象。下面的代碼段中:
>>> class ObjectCreator(object): ... pass ...
python在內(nèi)存中產(chǎn)生了一個(gè)名叫做"ObjectCreator"的對(duì)象。這個(gè)對(duì)象(類(lèi))自身?yè)碛挟a(chǎn)生對(duì)象(實(shí)例instance)的能力。 這就是為什么稱(chēng)呼這東西(后面遇到容易混淆的地方,我們稱(chēng)之為:類(lèi)對(duì)象)也是類(lèi)的原因。同時(shí),它也是一個(gè)對(duì)象,因此你可以對(duì)它做如下操作:
賦值給變量
復(fù)制它
為它增加屬性(attribute)
作為參數(shù)傳值給函數(shù)
舉例:
>>> print(ObjectCreator) # 你可以打印一個(gè)類(lèi),因?yàn)樗瑫r(shí)也是對(duì)象>>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # 作為參數(shù)傳值給函數(shù) >>> print(hasattr(ObjectCreator, 'new_attribute')) False >>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class >>> print(hasattr(ObjectCreator, 'new_attribute')) True >>> print(ObjectCreator.new_attribute) foo >>> ObjectCreatorMirror = ObjectCreator # 將類(lèi)賦值給變量 >>> print(ObjectCreatorMirror.new_attribute) foo >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c>
動(dòng)態(tài)創(chuàng)建類(lèi)
既然類(lèi)也是對(duì)象,那么我們就可以在運(yùn)行的時(shí)候創(chuàng)建它,跟創(chuàng)建對(duì)象一樣自然。
首先,我們使用class關(guān)鍵字定義一個(gè)產(chǎn)生類(lèi)的函數(shù):
>>> def choose_class(name): ... if name == 'foo': ... class Foo(object): ... pass ... return Foo # return the class, not an instance ... else: ... class Bar(object): ... pass ... return Bar ... >>> MyClass = choose_class('foo') >>> print(MyClass) # the function returns a class, not an instance>>> print(MyClass()) # you can create an object from this class <__main__.Foo object at 0x89c6d4c>
這很容易理解吧。但是,這并不那么動(dòng)態(tài)啊。我們還是需要自己來(lái)寫(xiě)這個(gè)類(lèi)的代碼。
既然類(lèi)也是對(duì)象,那就應(yīng)該有用來(lái)產(chǎn)生它的東西。這東西就是type。
先來(lái)說(shuō)說(shuō)你所認(rèn)識(shí)的type。這個(gè)古老而好用的函數(shù),可以讓我們知道一個(gè)對(duì)象的類(lèi)型是什么。
>>> print(type(1))>>> print(type("1")) >>> print(type(ObjectCreator)) >>> print(type(ObjectCreator()))
實(shí)際上,type還有一個(gè)完全不同的功能,它可以在運(yùn)行時(shí)產(chǎn)生類(lèi)。type可以傳入一些參數(shù),然后返回一個(gè)類(lèi)。(好吧,必須承認(rèn),根據(jù)不同的傳入?yún)?shù),一個(gè)相同的函數(shù)type居然會(huì)有兩個(gè)完全不同的作用,這很愚蠢。不過(guò)python這樣做是為了保持向后兼容性。)
下面舉例type創(chuàng)建類(lèi)的用法。首先,對(duì)于類(lèi)一般是這么定義的:
>>> class MyShinyClass(object): ... pass
在下面,MyShinyClass也可以這樣子被創(chuàng)建出來(lái),并且跟上面的創(chuàng)建方法有一樣的表現(xiàn):
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object >>> print(MyShinyClass)>>> print(MyShinyClass()) # create an instance with the class <__main__.MyShinyClass object at 0x8997cec>
type創(chuàng)建類(lèi)需要傳入三個(gè)參數(shù),分別為:
類(lèi)的名字
一組"類(lèi)的父類(lèi)"的元組(tuple) (這個(gè)會(huì)實(shí)現(xiàn)繼承,也可以為空)
字典 (類(lèi)的屬性名與值,key-value的形式,不傳相當(dāng)于為空,如一般寫(xiě)法中的pass).
下面來(lái)點(diǎn)復(fù)雜的,來(lái)更好的理解type傳入的三個(gè)參數(shù):
class Foo(object): bar = True def echo_bar(self): print(self.bar)
等價(jià)于:
def echo_bar(self): print(self.bar) Foo = type('Foo', (), {'bar':True, 'echo_bar': echo_bar})
想要看點(diǎn)有繼承關(guān)系的類(lèi)的實(shí)現(xiàn),來(lái):
class FooChild(Foo): pass
等價(jià)于:
FooChild = type('FooChild', (Foo, ), {})
回顧一下我們學(xué)到哪了: 在python中,類(lèi)就是對(duì)象,并且你可以在運(yùn)行的時(shí)候動(dòng)態(tài)創(chuàng)建類(lèi).
那到底什么是metaclass(元類(lèi))
metaclass 就是創(chuàng)建類(lèi)的那家伙。(事實(shí)上,type就是一個(gè)metaclass)
我們知道,我們定義了class就是為了能夠創(chuàng)建object的,沒(méi)錯(cuò)吧?
我們也學(xué)習(xí)了,python中類(lèi)也是對(duì)象。
那么,metaclass就是用來(lái)創(chuàng)造“類(lèi)對(duì)象”的類(lèi).它是“類(lèi)對(duì)象”的“類(lèi)”。
可以這樣子來(lái)理解:
MyClass = MetaClass() MyObject = MyClass()
也可以用我們上面學(xué)到的type來(lái)表示:
MyClass = type('MyClass', (), {})
說(shuō)白了,函數(shù)type就是一個(gè)特殊的metaclass.
python在背后使用type創(chuàng)造了所有的類(lèi)。type是所有類(lèi)的metaclass.
我們可以使用__class__屬性來(lái)驗(yàn)證這個(gè)說(shuō)法.
在python中,一切皆為對(duì)象:整數(shù)、字符串、函數(shù)、類(lèi).所有這些對(duì)象,都是通過(guò)類(lèi)來(lái)創(chuàng)造的.
>>> age = 35 >>> age.__class__>>> name = 'bob' >>> name.__class__ >>> def foo(): pass >>> foo.__class__ >>> class Bar(object): pass >>> b = Bar() >>> b.__class__
那么,__class__的__class__又是什么呢?
>>> age.__class__.__class__>>> name.__class__.__class__ >>> foo.__class__.__class__ >>> b.__class__.__class__
metaclass就是創(chuàng)造類(lèi)對(duì)象的工具.如果你喜歡,你也可以稱(chēng)之為"類(lèi)的工廠".
type是python內(nèi)置的metaclass。不過(guò),你也可以編寫(xiě)自己的metaclass.
__metaclass__ 屬性
我們可以在一個(gè)類(lèi)中加入 __metaclass__ 屬性.
class Foo(object): __metaclass__ = something... [...]
當(dāng)你這么做了,python就會(huì)使用metaclass來(lái)創(chuàng)造類(lèi):Foo。
注意啦,這里有些技巧的。
當(dāng)你寫(xiě)下class Foo(object)的時(shí)候,類(lèi)對(duì)象Foo還沒(méi)有在內(nèi)存中生成。
python會(huì)在類(lèi)定義中尋找__metaclass__ 。如果找到了,python就會(huì)使用這個(gè)__metaclass__ 來(lái)創(chuàng)造類(lèi)對(duì)象: Foo。如果沒(méi)找到,python就使用type來(lái)創(chuàng)造Foo。
請(qǐng)把下面的幾段話重復(fù)幾遍:
當(dāng)你寫(xiě)如下代碼的時(shí)候:
class Foo(Bar): pass
python做了以下事情:
Foo中有__metaclass__這個(gè)屬性嗎?
如果有,python會(huì)在內(nèi)存中通過(guò)__metaclass__創(chuàng)建一個(gè)名字為Foo的類(lèi)對(duì)象。
如果python沒(méi)有在Foo中找到__metaclass__,它會(huì)繼續(xù)在Bar(父類(lèi))中尋找__metaclass__,并嘗試做和前面同樣的操作。
如果python由下往上遍歷父類(lèi)也都沒(méi)有找不到__metaclass__,它就會(huì)在模塊(module)中去尋找__metaclass__,并嘗試做同樣的操作。
如果還是沒(méi)有找不到__metaclass__, python才會(huì)用內(nèi)置的type(這也是一個(gè)metaclass)來(lái)創(chuàng)建這個(gè)類(lèi)對(duì)象。
現(xiàn)在問(wèn)題來(lái)了,我們要怎么用代碼來(lái)實(shí)現(xiàn)__metaclass__呢? 寫(xiě)一些可以用來(lái)產(chǎn)生類(lèi)(class)的東西就行。
那什么可以產(chǎn)生類(lèi)?無(wú)疑就是type,或者type的任何子類(lèi),或者任何使用到type的東西都行.
自定義metaclass
使用metaclass的主要目的,是為了能夠在創(chuàng)建類(lèi)的時(shí)候,自動(dòng)地修改類(lèi)。
一個(gè)很傻的需求,我們決定要將該模塊中的所有類(lèi)的屬性,改為大寫(xiě)。
有幾種方法可以做到,這里使用__metaclass__來(lái)實(shí)現(xiàn).
在模塊的層次定義metaclass,模塊中的所有類(lèi)都會(huì)使用它來(lái)創(chuàng)造類(lèi)。我們只需要告訴metaclass,將所有的屬性轉(zhuǎn)化為大寫(xiě)。
# type也是一個(gè)類(lèi),我們可以繼承它. class UpperAttrMetaclass(type): # __new__ 是在__init__之前被調(diào)用的特殊方法 # __new__是用來(lái)創(chuàng)建對(duì)象并返回這個(gè)對(duì)象 # 而__init__只是將傳入的參數(shù)初始化給對(duì)象 # 實(shí)際中,你很少會(huì)用到__new__,除非你希望能夠控制對(duì)象的創(chuàng)建 # 在這里,類(lèi)是我們要?jiǎng)?chuàng)建的對(duì)象,我們希望能夠自定義它,所以我們改寫(xiě)了__new__ # 如果你希望的話,你也可以在__init__中做些事情 # 還有一些高級(jí)的用法會(huì)涉及到改寫(xiě)__call__,但這里我們就先不這樣. def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type(future_class_name, future_class_parents, uppercase_attr)
這里的方式其實(shí)不是OOP(面向?qū)ο缶幊?.因?yàn)槲覀冎苯诱{(diào)用了type,而不是改寫(xiě)父類(lèi)的__type__方法.
所以我們也可以這樣子處理:
class UpperAttrMetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
這樣子看,我們只是復(fù)用了 type.__new__方法,這就是我們熟悉的基本的OOP編程,沒(méi)什么魔法可言.
你可能注意到,__new__方法相比于
type(future_class_name, future_class_parents, future_class_attr)
多了一個(gè)參數(shù): upperattr_metaclass, 請(qǐng)別在意,這沒(méi)什么特別的: __new__總是將"它要定義的類(lèi)"作為***個(gè)參數(shù)。
這就好比是 self 在類(lèi)的一般方法(method)中一樣,也是被作為***個(gè)參數(shù)傳入。
當(dāng)然啦,這里的名字的確是我起的太長(zhǎng)了。就像self一樣,所有的參數(shù)都有它們傳統(tǒng)的名稱(chēng)。因此,在實(shí)際的代碼中,一個(gè)metaclass應(yīng)該是寫(xiě)成下面樣子的:
(我們同時(shí)使用常見(jiàn)的super來(lái)讓代碼更清晰)
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attr = {} for name, val in attrs.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, attrs)
使用了 metaclass 的代碼是比較復(fù)雜,但我們使用它的原因并不是為了復(fù)雜, 而是因?yàn)槲覀兺ǔ?huì)使用 metaclass 去做一些晦澀的事情,比如, 依賴(lài)于自省,控制繼承等等。
確實(shí),用 metaclass 來(lái)搞些“黑魔法”是特別有用的,因而會(huì)復(fù)雜化代碼。
但就metaclass本身而言,它們其實(shí)是很簡(jiǎn)單的:中斷類(lèi)的默認(rèn)創(chuàng)建、修改類(lèi)、***返回修改后的類(lèi).
到底為什么要使用metaclass
現(xiàn)在我們面臨一個(gè)問(wèn)題: 為什么要使用metaclass? 它容易出錯(cuò)且晦澀難懂.
好吧,一般來(lái)說(shuō),我們根本就用不上它, 99%的用戶(hù)應(yīng)該根本不必為此操心。
實(shí)際用到metaclass的人,很清楚他們到底需要做什么,根本不用解釋為什么要用.
metaclass 的一個(gè)主要用途就是構(gòu)建API。Django(一個(gè)python實(shí)現(xiàn)的web框架)的ORM 就是一個(gè)例子。
用Django先定義了以下Model:
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
然后執(zhí)行下面代碼:
guy = Person.objects.get(name='bob') print guy.age # result is 35
這里打印的輸出并不是IntegerField,而是一個(gè)int,int是從數(shù)據(jù)庫(kù)中獲取的.
這是因?yàn)?models.Model 使用 __metaclass__來(lái)實(shí)現(xiàn)了復(fù)雜的數(shù)據(jù)庫(kù)查詢(xún)。但對(duì)于你看來(lái),這就是簡(jiǎn)單的API而已,不用關(guān)心背后的復(fù)雜工作。
上述就是小編為大家分享的python中metaclass的作用是什么了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。