Source From Here
Preface
這是一篇在 Stack overflow上很熱的帖子。提問者自稱已經掌握了有關Python OOP編程中的各種概念,但始終覺得元類 (metaclass) 難以理解。他知道這肯定和自省有關,但仍然覺得不太明白,希望大家可以給出一些實際的例子和代碼片段以幫助理解,以及在什麼情況下需要進行元編程。於是e-satis同學給出了神一般的回复,該回复獲得了985點的讚同點數,更有人評論說這段回复應該加入到Python的官方文檔中去。而e-satis同學本人在Stack Overflow中的聲望積分也高達64271分。以下就是這篇精彩的 回复!
類也是對象
在理解 metaclass 之前,你需要先掌握 Python 中的類。Python 中類的概念借鑒於 Smalltalk,這顯得有些奇特。在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段。在 Python 中這一點仍然成立:
但是,Python 中的類還遠不止如此。
類同樣也是一種對象。是的,沒錯,就是對象。只要你使用關鍵字 class,Python 解釋器在執行的時候就會創建一個對象。下面的代碼段:
將在內存中創建一個對象,名字就是
ObjectCreator。這個對象(類)自身擁有創建對象(類實例)的能力,而這就是為什麼它是一個類的原因。但是,它的本質仍然是一個對象,於是乎你可以對它做如下的操作:
你可以參考下面範例更了解 metaclass 也是一個物件的事實:
動態地創建類
因為類也是對象,你可以在運行時動態的創建它們,就像其他任何對像一樣。首先,你可以在函數中創建類,使用 class 關鍵字即可。
但這還不夠動態,因為你仍然需要自己編寫整個類的代碼。由於類也是對象,所以它們必須是通過什麼東西來生成的才對。當你使用
class 關鍵字時,Python 解釋器自動創建這個對象。但就和 Python 中的大多數事情一樣,Python 仍然提供給你手動處理的方法。還記得內建函數 type 嗎?這個古老但強大的函數能夠讓你知道一個對象的類型是什麼,就像這樣:
Preface
這是一篇在 Stack overflow上很熱的帖子。提問者自稱已經掌握了有關Python OOP編程中的各種概念,但始終覺得元類 (metaclass) 難以理解。他知道這肯定和自省有關,但仍然覺得不太明白,希望大家可以給出一些實際的例子和代碼片段以幫助理解,以及在什麼情況下需要進行元編程。於是e-satis同學給出了神一般的回复,該回复獲得了985點的讚同點數,更有人評論說這段回复應該加入到Python的官方文檔中去。而e-satis同學本人在Stack Overflow中的聲望積分也高達64271分。以下就是這篇精彩的 回复!
類也是對象
在理解 metaclass 之前,你需要先掌握 Python 中的類。Python 中類的概念借鑒於 Smalltalk,這顯得有些奇特。在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段。在 Python 中這一點仍然成立:
- class ObjectCreator(object):
- pass
- my_object = ObjectCreator()
- print my_object
- class ObjectCreator(object):
- pass
你可以參考下面範例更了解 metaclass 也是一個物件的事實:
- # 你可以打印一個類,因為它其實也是一個對象
- print ObjectCreator # <class '__main__.ObjectCreator'>
- def echo(o):
- print o
- echo(ObjectCreator) # 你可以將類做為參數傳給函數
- print hasattr(ObjectCreator, 'new_attribute') # Fasle
- ObjectCreator.new_attribute = 'foo' # 你可以為類增加屬性
- print hasattr(ObjectCreator, 'new_attribute') # True
- objectCreatorMirror = ObjectCreator # 你可以將類賦值給一個變量
- print objectCreatorMirror() # <__main__ .objectcreator="" object...="">
因為類也是對象,你可以在運行時動態的創建它們,就像其他任何對像一樣。首先,你可以在函數中創建類,使用 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 # 函數返回的是類,不是類的實例: <class '__main__.Foo'>
- print MyClass() # 你可以通過這個類創建類實例,也就是對象: <__main__ .foo="" object...="">
- print type(1) #
'int'> - print type("1") #
'str'> - print type(ObjectCreator) #
'type' >
- type(類名, 父類的元组(針對繼承的情况,可以為空),包含屬性的字典(名稱與值))
- class MyShinyClass(object):
- pass
- MyShinyClass = type('MyShinyClass', (), {}) # 返回一个類對象: <class '__main__.MyShinyClass'>
- print MyShinyClass
- print MyShinyClass() # 創建一個該類的實例: <__main__ .myshinyclass="" object...="">
- class Foo(object):
- bar = True
- Foo2 = type('Foo', (), {'name':True})
- # <class '__main__.Foo'> has attr:bar? True, attr:name? False
- print "%s has attr:bar? %s, attr:name? %s" % (Foo, hasattr(Foo, 'bar'), hasattr(Foo, 'name'))
- # <class '__main__.Foo'> has attr:bar? False, attr:name? True
- print "%s has attr:bar? %s, attr:name? %s" % (Foo2, hasattr(Foo2, 'bar'), hasattr(Foo2, 'name'))
- f2 = Foo2()
- print "f2.name=%s; Foo2.name=%s" % (f2.name, Foo2.name)
- f2.name = 'Peter'
- print "f2.name=%s; Foo2.name=%s" % (f2.name, Foo2.name)
當然,你可以向這個類繼承,所以,如下的代碼:
- class FooChild(Foo):
- pass
- FooChild = type('FooChild', (Foo,),{})
- print FooChild # <class '__main__.FooChild'>
- print FooChild.__base__ # <class '__main__.Foo'>
- print FooChild.bar # bar 屬性是從 Foo 繼承而來
- def echo_bar(self):
- print self.bar
- FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
- print "Foo has method:echo_bar? %s" % (hasattr(Foo, 'echo_bar'))
- print "FooChild has method:echo_bar? %s" % (hasattr(FooChild, 'echo_bar'))
- my_foo = FooChild()
- my_foo.echo_bar()
你可以看到,在Python中,類也是對象,你可以動態的創建類。這就是當你使用關鍵字 class 時Python在幕後做的事情,而這就是通過元類來實現的。
到底什麼是元類
metaclass (元類) 就是用來創建類的“東西”。你創建類就是為了創建類的實例對象,不是嗎?但是我們已經學習到了Python中的類也是對象。好吧,元類就是用來創建這些類(對象)的,元類就是類的類,你可以這樣理解為:
- MyClass = MetaClass() # MetaClass 建立 Class
- MyObject = MyClass() # Class 建立 Object
- MyClass = type('MyClass', (), {})
現在,對於任何一個 __class__ 的 __class__ 屬性又是什麼呢?
因此 metaclass 就是創建類這種對象的東西。如果你喜歡的話,可以把它稱為 "類工廠" type 就是 Python 的內建 metaclass,當然你也可以創建自己的元類。
__metaclass__ 屬性
你可以在寫一個類的時候為其添加 __metaclass__ 屬性。
- class Foo(object):
- __metaclass__ = something…
- […]
- class Foo(Bar):
- pass
現在的問題就是,你可以在 __metaclass__ 中放置些什麼代碼呢?答案就是:可以創建一個類的東西。那麼什麼可以用來創建一個類呢?type,或者任何使用到 type 或者子類化type 的東東都可以。
自定義元類
元類的主要目的就是為了當創建類時能夠自動地改變類。通常,你會為API做這樣的事情,你希望可以創建符合當前上下文的類。假想一個很傻的例子,你決定在你的模塊裡所有的類的屬性都應該是大寫形式。有好幾種方法可以辦到,但其中一種就是通過在模塊級別設定 __metaclass__。採用這種方法,這個模塊中的所有類都會通過這個元類來創建,我們只需要告訴元類把所有的屬性都改成大寫形式就萬事大吉了。
幸運的是,__metaclass__ 實際上可以被任意調用,它並不需要是一個正式的類(我知道,某些名字裡帶有'class'的東西並不需要是一個class,畫畫圖理解下,這很有幫助)。所以,我們這裡就先以一個簡單的函數作為例子開始。
- # -*- coding: utf-8 -*-
- # 元類會自動將你通常傳给 'type' 的參數作為自己的參數傳入
- def upper_attr(future_class_name, future_class_parents, future_class_attr):
- '''返回一个类对象,将属性都转为大写形式'''
- # 選擇所有不以'__'開頭的屬性
- attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
- # 将它们转为大写形式
- uppercase_attr = dict((name.upper(), value) for name, value in attrs)
- # 通過 'type' 來做類對象的創建
- return type(future_class_name, future_class_parents, uppercase_attr)
- class Foo(object):
- # 在這裡定義 __metaclass__,這樣就只會作用於這個類中
- __metaclass__ = upper_attr
- bar = 'bip'
- print hasattr(Foo, 'bar')
- # 输出: False
- print hasattr(Foo, 'BAR')
- # 输出:True
- f = Foo()
- print f.BAR
- # 输出:'bip'
- # 請記住,'type' 實際上是一個類,就像 'str' 和 'int' 一樣
- # 所以,你可以從 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):
- attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
- uppercase_attr = dict((name.upper(), value) for name, value in attrs)
- return type(future_class_name, future_class_parents, uppercase_attr)
- class UpperAttrMetaClass(type):
- # __new__ 是在__init__之前被調用的特殊方法
- # __new__ 是用来創建對象並返回之的方法
- # 而 __init__ 只是用来將傳入的参數初始化给對象
- # 你很少用到 __new__,除非你希望能够控制對象的創建
- # 這里,創建的對象是類,我们希望能够自定義它,所以我們這裡改寫 __new__
- # 如果你希望的话,你也可以在 __init__ 中做些事情
- # 還有一些高级的用法會涉及到改寫 __call__ 特殊方法,但是我们這里不用
- def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
- attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
- uppercase_attr = dict((name.upper(), value) for name, value in attrs)
- # 重用 type.__new__ 方法
- # 這就是基本的 OOP 编程
- return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
- class UpperAttrMetaclass(type):
- def __new__(cls, name, bases, dct):
- attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
- uppercase_attr = dict((name.upper(), value) for name, value in attrs)
- return type.__new__(cls, name, bases, uppercase_attr)
- class UpperAttrMetaclass(type):
- def __new__(cls, name, bases, dct):
- attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
- uppercase_attr = dict((name.upper(), value) for name, value in attrs)
- return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
為什麼要用 metaclass 類而不是函數?
由於 __metaclass__ 可以接受任何可調用的對象,那為何還要使用類呢,因為很顯然使用類會更加複雜啊?這裡有好幾個原因:
究竟為什麼要使用元類?
現在回到我們的大主題上來,究竟是為什麼你會去使用這樣一種容易出錯且晦澀的特性?好吧,一般來說,你根本就用不上它:
元類的主要用途是創建 API。一個典型的例子是 Django ORM。它允許你像這樣定義:
- class Person(models.Model):
- name = models.CharField(max_length=30)
- age = models.IntegerField()
- guy = Person(name='bob', age='35')
- print guy.age
結語
首先,你知道了 類 其實是能夠創建出 類實例 的對象。好吧,事實上類本身也是實例,當然,它們是 metaclass 的實例。Python 中的一切都是對象,它們要么是類的實例,要么是元類的實例,除了 type。type 實際上是它自己的元類,在純 Python 環境中這可不是你能夠做到的,這是通過在實現層面耍一些小手段做到的。其次,元類是很複雜的。對於非常簡單的類,你可能不希望通過使用元類來對類做修改。你可以通過其他兩種技術來修改類:
當你需要動態修改類時,99%的時間裡你最好使用上面這兩種技術。當然了,其實在99%的時間裡你根本就不需要動態修改類:D
Supplement
* Stackoverflow - What is a metaclass in Python?
* The Python Tutorial » Classes
* The Python Language Reference » Data model
沒有留言:
張貼留言