add by zhj: 這是大stackoverflow上一位小白提出的問題,好吧,我承認我也是小白,元類這塊我也是好多次想搞明白,
但終究因為太難懂而敗下陣來。看了這篇文章明白了許多,再加下啄木鳥社區的 Python 類型和對象 。卧槽,
這簡直就是珠聯璧合,日月神劍啊,尼瑪。終於理解了元類。元類就是創建類對象的類,建議用__metaclass__用於指定元類,它的好
處也就是可以要創建類之前和之后修改類的屬性,創建后修改屬性也可以在__init__中做,不過完全可以用__new__代替,在元類的
__new__()方法中會直接或間接調用type(classname, parentclasses , attrs),我們可以控制這三個參數,type()就是實例化type類,
即在堆上創建一個類對象,任何類對象的創建都會調用這個接口,我們可以控制讓__new__()返回什么樣的類對象,比如我們可以在
__new__多次調用type(),讓他生成多個類對象,然后將這些類按一定的策略組合起來返回。說白了,__metaclass__是用於控制類的
生成過程。我們大多數情況下無需指定__metaclass__,這種情況下,解釋器會按一定的策略找到元類。元類的參數就是我們用class關鍵字
定義類時的類名,類的所有父類,類中定義的{屬性名:屬性對象}。
翻譯時部分地方有修改。
原文:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python?answertab=votes#tab-top
類是對象
在理解元類之前,你需要先掌握Python中的類。在Python中,對類的定義比較特殊,這點借鑒了Smalltalk語言。在大多
數語言中,類只是用於描述如何創建對象的代碼片,在Python中,在一定程度上也是這樣:
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
不過,Python中的類不止如此。類也是對象,是的,我沒說錯,在《Python源碼剖析》中提到除了Python的內置類型外
其它對象都是在堆上創建的,而內置類型應該是在內存的全局數據區。在Python中,一切都是對象,對象對用戶來說只提供
了變量,變量就是那些標識符,變量以引用的方式操作對象,變量相當於對象暴露給用戶的接口。Python中的引用跟C++
的引用差不多相同,不過也有差異。
當你使用關鍵字class時,Python解釋器執行時,會創建一個對象。如下,Python會在堆中創建一個對象,並在符號表
中增加一個標識符ObjectCreator,指向那個對象的地址,這就是Python中所說的引用,我們一般稱ObjectCreator為變量。
>>> class ObjectCreator(object): ... pass ...
我們稱這個對象為類對象,因為該對象可以實例化,創建實例對象,因此我們稱它為類。
因為它是一個對象,所以:
- 可以將它賦給一個變量
- 可以copy它
- 可以給它增加屬性
- 可以作為函數參數
比如
>>> print(ObjectCreator) # 可以print it <class '__main__.ObjectCreator'> >>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # 類對象作為函數參數 <class '__main__.ObjectCreator'> >>> print(hasattr(ObjectCreator, 'new_attribute')) False >>> ObjectCreator.new_attribute = 'foo' # 增加類的屬性 >>> 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>
通過type類創建類
原文中說通過type類創建對象是動態,其實我們平時使用class關鍵字定義類也是動態的,當Python解釋器遇到class關鍵字的,
它就會執行,並創建類對象。其實,類對象也是實例化的結果,即類對象是由另一個類實例化得到的,我們稱創建類的類為元類,
反之也成立,如果類X實例化后得到是類對象,那X就是元類。在Python中,只有type類及其子類才可以當元類。多說
一句,這里會有雞生蛋、蛋生雞的問題。在Python中,一切都是對象,一切對象都是類實例化的結果。對象由類實例化得到,而類也是
對象,它也是由類(元類)實例化得到,繼續向上,元類也是對象,也要由另一個元類實例化得到,這樣下去就沒有盡頭了。在Python中,
這個追溯終止在type類。元類是type類或其子類,而type類的元類就是它自己,哈哈,type類自己創建自己,牛逼吧,當然type類的
這個特性是Python設計者設計並實現的。至於Python解釋器在定義類時是怎么找到該類的元類的,后面我們會講。
還記得type()方法嗎?我們常用它看一個對象X所屬的類,即創建該對象X的類,當對象X是類對象時,看到的就是元類。如下,
>>> print(type(1)) <type 'int'> >>> print(type("1")) <type 'str'> >>> print(type(ObjectCreator)) #查看創建ObjectCreator類的類 <type 'type'> >>> print(type(ObjectCreator())) <class '__main__.ObjectCreator'>
type類還有另外一個功能,它可以創建類,它以類的一些信息做為type()的參數,並返回一個類。我知道,type根據輸入參數的不同而有不同的功能,
這種做法是愚蠢的。當type()只有一個參數時,它的功能就是返回創建該參數的類;當多於一個參數時,type()才是type類的實例化,實例化得到
一個類並返回該類。type創建類時,參數格式如下,classname是類名,字符串類型,parentclasses是類所有父類,元組類型,attrs是類的所有{屬性:值},
字典類型。如果用戶是用class關鍵字定義的類,那解釋器會自動轉為下面的格式執行。
type(classname, parentclasses , attrs)
比如,
>>> class MyShinyClass(object): ... pass
當解釋器執行時,會轉為下面的語句,當然,你也可以直接這么寫。
MyShinyClass = type('MyShinyClass', (object,), {})
下面我們來定義一個類,並在類中定義屬性,如
>>> class Foo(object): ... bar = True
它會被翻譯成下面的形式,
>>> Foo = type('Foo', (), {'bar':True})
用用看吧
>>> print(Foo) <class '__main__.Foo'> >>> print(Foo.bar) True >>> f = Foo() >>> print(f) <__main__.Foo object at 0x8a9b84c> >>> print(f.bar) True
我們可以用另一個類繼承它,so:
>>> class FooChild(Foo): ... pass
would be:
>>> FooChild = type('FooChild', (Foo,), {}) >>> print(FooChild) <class '__main__.FooChild'> >>> print(FooChild.bar) # bar is inherited from Foo True
OK,你會想給你的類增加方法。定義一個函數,並將它分配給類的屬性
>>> def echo_bar(self): ... print(self.bar) ... >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) >>> hasattr(Foo, 'echo_bar') False >>> hasattr(FooChild, 'echo_bar') True >>> my_foo = FooChild() >>> my_foo.echo_bar() True
說到這里,你應該已經明白了類的創建過程。那Python創建類都是這么簡單嗎?都是直接用type元類實例化得到?
不是的,你可以指定元類,我們會在“__metaclass__屬性”一節會講。
什么是元類
元類就是創建類的類,當通過type.__new__(cls, classname, bases, attrs)創建類時,cls就是該類的元類,
它是type類或其子類。在上面,我們提到可以使用type()查看類的元類,你也可以使用__class__,他們是完全等價的。
一切類的創建最終都會調用type.__new__(cls, classname, bases, attrs),它會在堆中創建一個類對象,並返回
>>> 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__是什么呢?__class__返回的是一個類對象,再一次__class__返回創建類對象的類,
即類的元類,如下,這個例子中類的元類都是type類,我們平時用的絕大部分類的元類都是type,不過也有例外,
有些類的元類是type的子類。當然,如果你對某個類一直調用__class__,那在有限次之后,它返回的就是type類了。
>>> age.__class__.__class__ <type 'type'> >>> name.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
__metaclass__屬性
你可以在類中添加__metaclass__屬性,用它指定創建該類的元類,前面我們說過,元類必須是type類或type子類。
注:其實__metaclass__只要是一個可調用對象就行,該可調用對象的參數格式為callable(classname, parentclasses , attrs),
可以看到,這跟上一節用type類創建類對象時,參數格式完全相同。Python要求該對象執行時必須要調用執行type()或type子類(),
當Python解釋器執行時,會調用這個可調用對象,參數也是Python解釋器添加上去的。一般的,__metaclass__用元類,在本
文中,作者分別用了函數和元類。這里還有一點,子類如果指定了__metaclass__,該__metaclass__必須與父類的元類相同或
是父類元類的子類,不然,呵呵,Python解釋器會選其中一個做元類,但選哪個,貌似沒有什么規律。
如果我們創建類時,要指定__metaclass__屬性,那最好創建的這個類的父類的元類是type類(有點繞),這樣就不容易出錯,
不然的話,請慎重使用元類。
class Foo(father_class): __metaclass__ = something... [...]
我們假定在繼承關系上,子類的元類是父類的元類或父類的元類的子類。在這個正確的前提下,我們說一下Python解釋器是怎么確定
一個類的元類的(只討論新式類的創建,舊式類不討論,對於新式類,Python解釋器的第三步是直接使用type類,作者討論了定義類時,
沒有父類的情況,這種類我們不會用到,不討論)。add by zhj:但后面我發現,下面這個規律並不成立,在"到底為什么要使用元類"
一節我猜想了更可能的規律,並初步驗證過了。
1、在類中查找__metaclass__屬性,如果找到就用,如果沒有,進入2
2、在繼承樹中查找__metaclass__屬性,如果還是沒有,進入3
3、使用type類
舉個例子吧,比如你想將一個類的屬性全部轉為大寫(以__開頭的屬性除外),其實一種辦法就是使用__metaclass__屬性。
前面我們提到過,__metaclass__可以是任意可調用對象,只要調用super()或super子類()就可以。
OK,我們先使用一個函數做__metaclass__,在實際上我們一般不會用函數,而是用類。
# the metaclass will automatically get passed the same argument # that you usually pass to `type` def upper_attr(future_class_name, future_class_parents, future_class_attr): """ Return a class object, with the list of its attribute turned into uppercase. """ print "call upper_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 # let `type` do the class creation return type(future_class_name, future_class_parents, uppercase_attr) class Foo(object): __metaclass__ = upper_attr bar = 'bip' print(hasattr(Foo, 'bar')) # Out: False print(hasattr(Foo, 'BAR')) # Out: True f = Foo() print(f.BAR) # Out: 'bip'
執行輸出如下:
>>>
call upper_attr
False
True
bip
>>>
然后我們將函數換成類,__new__方法是靜態方法,當創建類時,Python解釋器會實例化元類
UpperAttrMetaclass(classname, parentclasses , attrs),進一步它會執行
UpperAttrMetaclass.__new__(UpperAttrMetaclass, classname, parentclasses , attrs)方法。我們在下面看到,__new__
會調用type類,其實指定__metaclass__的好處也就是可以要創建類之前修改類的屬性,即在調用type(classname, parentclasses , attrs)
之前修改這三個參數,前兩個參數貌似沒啥好修改的,主要是修改第三個參數,第三個參數是一個字典,我們可以修改鍵,
即修改屬性名稱,也可以修改鍵值,即屬性對象,屬性對象有value/property/method。
Django的ORM就是這樣干的,我們在Model中定義的字段類型是各種field類實例,但當創建該model類時,會將這些field類實例轉為Python
內置的類型,如CharField()實例轉為unicode或str類型,IntegerField()實例轉為int型。
# remember that `type` is actually a class like `str` and `int` # so you can inherit from it class UpperAttrMetaclass(type): # __new__ is the method called before __init__ # it's the method that creates the object and returns it # while __init__ just initializes the object passed as parameter # you rarely use __new__, except when you want to control how the object # is created. # here the created object is the class, and we want to customize it # so we override __new__ # you can do some stuff in __init__ too if you wish # some advanced use involves overriding __call__ as well, but we won't # see this 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(),我們沒有使用super().__new__,下面我們修改一下
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = {} for name, val in attrs.items(): if not name.startswith('__'): uppercase_attrs[name.upper()] = val else: uppercase_attrs[name] = val return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attrs)
元類可以做到:
- 攔截類的生成
- 修改類
- 返回修改后的類
__metaclass__為什么要用類而不是函數
__metaclass__可以接受任意可調用對象,為什么要使用類而不是函數呢?有下面幾個原因
- 意圖清晰。當你讀到
UpperAttrMetaclass(type)
, 你知道接下來要做什么 - 你可以使用OOP
- 你可以重定義
__new__
,__init__
和__call__,不過其實你完成都在
__new__方法中完成。有些人更喜歡用
__init__
到底為什么要使用元類
那么問題來了,用元類誰最強?哈哈,開個玩笑。為什么你要使用容易出錯的元類呢?
well,通常情況下你不需要使用
元類是一個高級特性,99%的用戶不需要關心。如果你還在想是否需要它,那你其實不需要(真正需要使用元類的人是非常確定自己的確需要的).
Python大師Tim Peters說過:元類主要的使用場景是創建API,一個典型的例子是Django ORM。
在Django ORM中,可以如下定義model
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
但是當你下面這樣做時
guy = Person(name='bob', age='35') print(guy.age)
它並不會返回一個IntegerField
實例,而是返回一個int實例。之所以會這樣,是因為models.Model
定義了元類
(不過它並沒有直接使用__metaclass__屬性,而是使用的另一種方法定義的元類),並且它使用一些魔法將你
用幾條簡單語句定義的Person類轉為數據庫的字段。Django通過暴露給用戶簡單的API,並通過使用元類,使復雜
的事情對用戶來說變得簡單。我看了一下源碼,如下。
class Model(six.with_metaclass(ModelBase)): _deferred = False def __init__(self, *args, **kwargs): signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs) # Set up the storage for instance state self._state = ModelState() …… ……
def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" return meta("NewBase", bases, {})
class ModelBase(type): """ Metaclass for all models. """ def __new__(cls, name, bases, attrs): super_new = super(ModelBase, cls).__new__ …… ……
six.with_metaclass(ModelBase)等價於ModelBase("NewBase", (), {}),第二個參數為空時,Python解釋器會認為該類繼承於object類,
所以這句話等價於ModelBase("NewBase", (object,), {}),我在測試中發現創建的NewBase類的元類是ModelBase類,但Python
並沒有給NewBase創建__metaclass__字段,這讓人疑惑,看來上面說的一個類創建時查找元類的規律並不成立。那Python解釋器到底是怎
么確定NewBase的元類就是ModelBase呢?經我初步驗證,應該是靠調用type.__new__(cls, classname, bases, attrs)方法時,第一個參數,
因為一切類對象最終都是通過這個方法創建的,在創建時,它的第一個參數就是被創建的類的元類,第一個參數一般是type類或其子類,類創建時會記
錄下它的元類。你可能會問,如果一個類是由多個類對象組合而成的呢?沒問題,如果最終的類的id與其中一個類相同,那說明並沒有創建新的類,
類的id不變,類的元類就不變,如果它的id與每個類都不同,那就是創建了一個新的類,創建時還是會調用type.__new__方法,第一個參數就是
它的元類。
我非常不喜歡ModelBase("NewBase", (object,), {})這種方式來定義類,因為這樣定義你很難知道NewBase的元類是誰,如果ModelBase
定義了__new__方法,且在調用type.__new__()時第一個參數是ModelBase,那NewBase的元類就是ModelBase,如果ModelBase沒有重定
義__new__,那對象創建時調用的type.__new__方法,第一個參數是type,即NewBase的元類就是type類。我操,好麻煩啊,我還是推薦傳統
的定義類的方式,如下,這樣才能一目了然的看到NewBase的元類是ModelBase,不過有一個要求,該NewBase的__metaclass__必須是NewBase
父類的元類的子類,或者相同,這樣才能確保NewBase的元類是__metaclass__指定的類。
class NewBase(object):
__metaclass__ = ModelBase
最后
看了這么多,是不是暈了,反正我有點暈,再次提醒大家,最好不要指定元類,也不要用調用type()或其子類的方式來定義類,
因為這塊很容易出錯。推薦大家還是有class關鍵字去定義類。
首先,你要知道類是能產生實例的對象。
實際上,類是元類的實例。
>>> class Foo(object): pass >>> id(Foo) 142630324
Python中的一切皆對象,這些對象分為兩種:類對象和實例對象。type類的元類就是它自己。
其次,元類是復雜的,對於類的簡單的改動是不需要使用元類的。你可以通過下面兩種技術來改變類:
- monkey patching
- 裝飾器
這里再說兩句吧,在gevent庫中就使用了monkey patching,它可以將Python標准庫中的IO接口
替換為自己的接口,感覺好牛逼的樣子,有時間看看它是怎么實現的。