這篇博客是我在stackoverflow上看了一個提問回覆後寫的,例子基本用的都是e-satis本人的例子,語言組織也基本按照翻譯來。python
但我並非一個翻譯者,並不會嚴格遵照每行每句的翻譯;
有時候我會將表述換個順序,省略一些我認爲可有可無的話,以便讀者更好理解。web
因此,若是你不喜歡個人語言表述,或者想要看英文原文,能夠去查看原回覆。數據庫
在理解metaclass以前,咱們先要掌握python中的類class
是什麼。
python中類的概念,是借鑑自smalltalk語言。
在大部分語言中,類指的是"描述如何產生一個對象(object)"的一段代碼,這對於python也是如此。編程
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
可是,在python中,類遠不止如此,類同時也是對象。
當你遇到關鍵詞class
的時候,python就會自動執行產生一個對象。下面的代碼段中:框架
>>> class ObjectCreator(object): ... pass ...
python在內存中產生了一個名叫作"ObjectCreator"的對象。這個對象(類)自身擁有產生對象(實例instance)的能力。 這就是爲何稱呼這東西(後面遇到容易混淆的地方,咱們稱之爲:類對象)也是類的緣由。同時,它也是一個對象,所以你能夠對它作以下操做:函數
舉例:工具
>>> print(ObjectCreator) # 你能夠打印一個類,由於它同時也是對象 <class '__main__.ObjectCreator'> >>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # 做爲參數傳值給函數 <class '__main__.ObjectCreator'> >>> 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 # 將類賦值給變量 >>> print(ObjectCreatorMirror.new_attribute) foo >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c>
既然類也是對象,那麼咱們就能夠在運行的時候建立它,跟建立對象同樣天然。學習
首先,咱們使用class
關鍵字定義一個產生類的函數:this
>>> 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 <class '__main__.Foo'> >>> print(MyClass()) # you can create an object from this class <__main__.Foo object at 0x89c6d4c>
這很容易理解吧。可是,這並不那麼動態啊。咱們仍是須要本身來寫這個類的代碼。spa
既然類也是對象,那就應該有用來產生它的東西。這東西就是type
。
先來講說你所認識的type
。這個古老而好用的函數,可讓咱們知道一個對象的類型是什麼。
>>> print(type(1)) <type 'int'> >>> print(type("1")) <type 'str'> >>> print(type(ObjectCreator)) <type 'type'> >>> print(type(ObjectCreator())) <class '__main__.ObjectCreator'>
實際上,type
還有一個徹底不一樣的功能,它能夠在運行時產生類。type
能夠傳入一些參數,而後返回一個類。(好吧,必須認可,根據不一樣的傳入參數,一個相同的函數type
竟然會有兩個徹底不一樣的做用,這很愚蠢。不過python這樣作是爲了保持向後兼容性。)
下面舉例type
建立類的用法。首先,對於類通常是這麼定義的:
>>> class MyShinyClass(object): ... pass 在下面,MyShinyClass也能夠這樣子被建立出來,而且跟上面的建立方法有同樣的表現: >>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object >>> print(MyShinyClass) <class '__main__.MyShinyClass'> >>> print(MyShinyClass()) # create an instance with the class <__main__.MyShinyClass object at 0x8997cec>
type
建立類須要傳入三個參數,分別爲:
下面來點複雜的,來更好的理解type
傳入的三個參數:
class Foo(object): bar = True def echo_bar(self): print(self.bar)
等價於:
def echo_bar(self): print(self.bar) Foo = type('Foo', (), {'bar':True, 'echo_bar': echo_bar})
想要看點有繼承關係的類的實現,來:
class FooChild(Foo): pass
等價於:
FooChild = type('FooChild', (Foo, ), {})
回顧一下咱們學到哪了: 在python中,類就是對象,而且你能夠在運行的時候動態建立類.
metaclass 就是建立類的那傢伙。(事實上,type
就是一個metaclass)
咱們知道,咱們定義了class就是爲了可以建立object的,沒錯吧?
咱們也學習了,python中類也是對象。
那麼,metaclass就是用來創造「類對象」的類.它是「類對象」的「類」。
能夠這樣子來理解:
MyClass = MetaClass() MyObject = MyClass()
也能夠用咱們上面學到的type
來表示:
MyClass = type('MyClass', (), {})
說白了,函數type
就是一個特殊的metaclass.
python在背後使用type
創造了全部的類。type
是全部類的metaclass.
咱們可使用__class__
屬性來驗證這個說法.
在python中,一切皆爲對象:整數、字符串、函數、類.全部這些對象,都是經過類來創造的.
>>> age = 35 >>> age.__class__ <type 'int'> >>> name = 'bob' >>> name.__class__ <type 'str'> >>> def foo(): pass >>> foo.__class__ <type 'function'> >>> class Bar(object): pass >>> b = Bar() >>> b.__class__ <class '__main__.Bar'>
那麼,__class__
的__class__
又是什麼呢?
>>> age.__class__.__class__ <type 'type'> >>> name.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
metaclass就是創造類對象的工具.若是你喜歡,你也能夠稱之爲"類的工廠".
type是python內置的metaclass。不過,你也能夠編寫本身的metaclass.
__metaclass__
屬性咱們能夠在一個類中加入 __metaclass__
屬性.
class Foo(object): __metaclass__ = something... ...... # 省略
當你這麼作了,python就會使用metaclass來創造類:Foo。
注意啦,這裏有些技巧的。
當你寫下class Foo(object)
的時候,類對象Foo尚未在內存中生成。
python會在類定義中尋找__metaclass__
。若是找到了,python就會使用這個__metaclass__
來創造類對象: Foo。若是沒找到,python就使用type來創造Foo。
請把下面的幾段話重複幾遍:
當你寫以下代碼的時候:
class Foo(Bar): pass
python作了如下事情:
Foo中有__metaclass__
這個屬性嗎?
若是有,python會在內存中經過__metaclass__
建立一個名字爲Foo的類對象。
若是python沒有在Foo中找到__metaclass__
,它會繼續在Bar(父類)中尋找__metaclass__
,並嘗試作和前面一樣的操做。
若是python由下往上遍歷父類也都沒有找不到__metaclass__
,它就會在模塊(module)中去尋找__metaclass__
,並嘗試作一樣的操做。
若是仍是沒有找不到__metaclass__
, python纔會用內置的type(這也是一個metaclass)來建立這個類對象。
如今問題來了,咱們要怎麼用代碼來實現__metaclass__
呢? 寫一些能夠用來產生類(class)的東西就行。
那什麼能夠產生類?無疑就是type
,或者type
的任何子類,或者任何使用到type
的東西都行.
使用metaclass的主要目的,是爲了可以在建立類的時候,自動地修改類。
一個很傻的需求,咱們決定要將該模塊中的全部類的屬性,改成大寫。
有幾種方法能夠作到,這裏使用__metaclass__
來實現.
在模塊的層次定義metaclass,模塊中的全部類都會使用它來創造類。咱們只須要告訴metaclass,將全部的屬性轉化爲大寫。
# type也是一個類,咱們能夠繼承它. class UpperAttrMetaclass(type): # __new__ 是在__init__以前被調用的特殊方法 # __new__是用來建立對象並返回這個對象 # 而__init__只是將傳入的參數初始化給對象 # 實際中,你不多會用到__new__,除非你但願可以控制對象的建立 # 在這裏,類是咱們要建立的對象,咱們但願可以自定義它,因此咱們改寫了__new__ # 若是你但願的話,你也能夠在__init__中作些事情 # 還有一些高級的用法會涉及到改寫__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)
這裏的方式其實不是OOP(面向對象編程).由於咱們直接調用了type,而不是改寫父類的__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)
這樣子看,咱們只是複用了 type.__new__
方法,這就是咱們熟悉的基本的OOP編程,沒什麼魔法可言.
你可能注意到,__new__
方法相比於
type(future_class_name, future_class_parents, future_class_attr)
多了一個參數: upperattr_metaclass, 請別在乎,這沒什麼特別的: __new__
老是將"它要定義的類"做爲第一個參數。
這就比如是 self 在類的通常方法(method)中同樣,也是被做爲第一個參數傳入。
固然啦,這裏的名字的確是我起的太長了。就像self同樣,全部的參數都有它們傳統的名稱。
所以,在實際的代碼中,一個metaclass應該是寫成下面樣子的:
(咱們同時使用常見的super來讓代碼更清晰)
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 的代碼是比較複雜,但咱們使用它的緣由並非爲了複雜, 而是由於咱們一般會使用 metaclass 去作一些晦澀的事情,好比, 依賴於自省,控制繼承等等。
確實,用 metaclass 來搞些「黑魔法」是特別有用的,於是會複雜化代碼。
但就metaclass自己而言,它們實際上是很簡單的:中斷類的默認建立、修改類、最後返回修改後的類.
如今咱們面臨一個問題: 爲何要使用metaclass? 它容易出錯且晦澀難懂.
好吧,通常來講,咱們根本就用不上它, 99%的用戶應該根本沒必要爲此操心。
實際用到metaclass的人,很清楚他們到底須要作什麼,根本不用解釋爲何要用.
metaclass 的一個主要用途就是構建API。Django(一個python寫的web框架)的ORM 就是一個例子。
用Django先定義瞭如下Model:
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
而後執行下面代碼:
guy = Person.objects.get(name='bob') print guy.age # result is 35
這裏打印的輸出並非IntegerField
,而是一個int
,int
是從數據庫中獲取的.
這是由於 models.Model
使用 __metaclass__
來實現了複雜的數據庫查詢。但對於你看來,這就是簡單的API而已,不用關心背後的複雜工做。
複習一下,咱們知道了,類是可以創造對象實例的對象,同時也是metaclass的對象實例(由於metaclass創造了它們).
在python中,一切皆爲對象。它們要麼是類的實例,要麼是metaclass的實例, 除了type。
type是它自身的metaclass。至因而怎麼實現的,總之純python語言是不可能實現的,這須要在實現層面上耍一些小手段才能作到的。
metaclass用起來比較複雜, 若是須要對很是簡單的類進行修改, 你可能不會使用它。有如下兩個技術能夠供你選擇: