Classes as objects
首先,在認識metaclass之前,你需要認識下python中的class。python中class的奇怪特性借鑑了smalltalk語言。大多數語言中,classes僅僅是用於描述怎樣創建一個對象的代碼端。在某種程度上說,python中的class也是這樣的。
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print my_object
<__main__.ObjectCreator object at 0x8974f2c>
但是,python中的classes同時還是objects,是的,看的沒錯,是objects,一旦你使用關鍵字class,python將執行並且生成一個對象(object),命令
>>> class ObjectCreator(object):
... pass
...
將在內存中創建一個名字爲ObjectCreator的對象。
這個對象(類)自己具有創建對象(實例)的能力,這也是爲什麼被稱之爲類。
因爲它是一個對象,所以它應該具有對象的一些特性:
- 你可以把它assign給一個變量
- 你可以copy它
- 你可以給它添加屬性
- 你可以把它作爲一個函數的參數
例如:
>>> print ObjectCreator # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print o
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<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 # you can assign a class to a variable
>>> print ObjectCreatorMirror.new_attribute
foo
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>
Creating classes dynamically
因爲classes是對象,所以你可以想對象一樣動態的創建他們。首先,你可以在一個函數中使用關鍵字創建它:
>>> 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>
但是,這個不是很動態,因爲你還是要自己寫全整個函數。因爲類是對象,所以它們可以被生成。當你使用關鍵字class的時候,python自動的創建這些對象,所以就像python中大部分事情一樣,我們同樣能夠手動的生成。
還記得函數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是這樣工作的:
type(name of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)
例如:
>>> class MyShinyClass(object):
... pass
可以利用以下方式手動創建:
>>> 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>
可以看出,我們以MyShinyClass作爲class的name, 函數很明顯了,只不過是些不同的參數變化,沒有理由去複雜化,用原文作者的話說:They can be different,but there is no reason to complicate things。
What are metaclasses (finally)
終於要開是解釋metaclasses了,我也等了好久,Metaclasses are the 'stuff' that creates classes. 這個是文章的作者說的,就是說它是個可以創建類的東東,你定義類是爲了創建對象是嗎?但是在python中我們視類爲對象,so,metaclasses就是來創建對象的。它們是類的類,你可以這樣想象它們: MyClass = MetaClass()
MyObject = MyClass()
你可以看出type讓你做了同樣的事情:
MyClass = type('MyClass', (), {})
這是因爲函數type事實上是一個metaclass,在python中,type是一個metaclass用於在後臺創建類。現在你知道爲什麼它爲什麼用的是小寫,而不是大寫的Type?well, 你是不是想到了str/int等函數呢,str用於創建字符串對象,int創建整型對象,那type只不過是創建類對象的類而已。你可以通過查看__class__屬性看到。所有的一切都是對象,所以的對象都可以通過一個函數創建:
>>> 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的創建者是誰呢?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
這下應該稍微明白了些吧,當然,我們也可以創建我們自己的metaclass。
The __metaclass__
attribute
你可以在創建類的時候添加__metaclass__屬性:
class Foo(object):
__metaclass__ = something...
[...]
python將使用metaclass去創建類Foo,要小心哦,你寫了class Foo(object),但是Foo還沒有在內存中創建,python會在你的類中尋找__metaclass__如果找到了就用它創建,如果沒有找到,它將使用type去創建類。
詳細點說吧,當你定義類時:
class Foo(object):
pass
python將首先查看在類定義中有沒有__metaclass__屬性,沒有將在parent中找,沒有去module級別找,如果還沒有找到,最後的殺招type。
好,最後的問題是,我們該在__metaclass__中放些什麼東東呢?
Custom metaclasses
一個metaclass的主要目的是在類創建的時候自動的去改變它,讀起來有點拗。一般來說,當你寫些APIs,而且這些APIs要滿足當前的上下文的時候,可以考慮使用metaclass。想象一下,當你的module中的類需要將它們的屬性寫爲小寫的時候,我們就可以試試在moudle級別metaclass,這時,module中的所有類都將由metaclass創建,我們所要做的是告訴metaclass將所有的屬性轉化爲小寫。幸運的是,我們不一定非要將__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.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
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)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
print hasattr(Foo, 'bar')
# Out: False
print hasattr(Foo, 'BAR')
# Out: True
f = Foo()
print f.BAR
# Out: 'bip'
現在,我們用一個類來實現metaclass:
# 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的__new__:
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
# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)
你可能已經注意到了額外的參數upperattr_metaclass,這個沒有什麼特別:一個方法常常將當前的實例作爲首個參數,就像平常的方法中的self。當然,我這裏用過長的名字視爲了更清晰的解釋,就像self一樣,所有的參數有慣例性的名字,所以一下是個產品性的metaclass:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.__new__(cls, clsname, bases, uppercase_attr)
我們可以使用super讓代碼更乾淨:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
就這麼多,metaclass就是這麼簡單。其實,使用metaclass的代碼複雜的原因不是因爲使用了metaclass,而是因爲你常常使用metaclass去做些需要introspection,操作繼承/變量比如__dict__等。事實上,metaclass也確實能做些複雜的事。但是,以下是不復雜的:
- 理解一個類的創建
- 改變類
- 返回一個修改的類
Why would you use metaclasses classes instead of functions?
還是堅持寫完吧!因爲__metaclass__能接受任意的調用,那爲什麼要選用看起來比較複雜的類呢?有一下幾個原因:- 興建類的目的比較清晰,參考上面的UpperAttrMetaclass(type)
- 你可以使用OOP。Metaclass能繼承自metaclass,覆蓋父類方法
- 你可以更好的組織你的代碼結構。make code easy to read
- 你可以使用__new__, __init__, __call__
- 都將做了metaclass,總要作點事吧!(這個比較牽強(:-)
Why the hell would you use metaclasses?
python guru寫的解釋道行太淺看不懂,不翻譯了,不過他說了個例子,就是Django中的ORM模型使用了,例如我們可以定義數據模型如下: 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通過expose 一個簡單的API,將許多複雜的事情讓我們看起來很簡單,而且通過metaclass,在後臺利用API重新生成code。
The last word
最後,作者提到了type的exception,還有class alterations:意思就是說我們大部分時間是用不到class alteration的,用到的時候呢,可以使用monkey patching和class decorators來實現,所以留給metaclass的空間很小了。全劇終!!!
文章的詳情清查看:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python
這個也寫的蠻不錯的:http://xiaocong.github.io/blog/2012/06/12/python-metaclass/
偶爾翻看stackoverflow的python帖子,受益匪淺,今天看到metaclass講的太好了,就試着翻譯出來試試,歡迎大家互相交流!