從前面"Python對象"文章中了解到,在Python中一切都是對象,類可以創建實例對象,但是類本身也是對象。
class C(object): pass c = C() print c.__class__ print C.__class__
代碼中,通過"__class__"屬性來查看對象的類型,對於類C對象本身,它的類型是type。
由於類也是對象,所以就可以在運行時動態的創建類,那么這時候就要用到內建函數type了。
再看type
從前面的文章了解到,可以通過內建函數type來獲取對象的類型。
class C(object): pass c = C() print c.__class__ is type(c) print C.__class__ is type(C)
這里,我們就看看內建函數type的另一個強大的功能,動態的創建類。當使用type創建類的時候,有以下形式:
type(類名, 父類的元組(可以為空), 屬性的字典)
- 要創建的class的名字
- 父類集合,如果只有一個父類,別忘了tuple的單元素寫法
- class的屬性字典
看看type創建類的例子:
def printInfo(self): print "%s is %d years old" %(self.name, self.age) S = type("Student", (object, ), {"name": "Wilber", "age": 28, "printStudentInfo": printInfo}) print type(S) s = S() print type(s) s.printStudentInfo()
例子中,通過type動態的創建了一個Studnent類,並且通過這個類可以創建實例:
__metaclass__
函數type實際上是一個元類,元類就是用來創建類的"模板"。我們可以通過類"模板"創建實例對象,同樣,也可以使用元類"模板"來創建類對象;也就是說,元類就是類的類。
在創建一個類的時候,可以設置"__metaclass__"屬性來指定元類。
"__metaclass__"屬性對應的代碼就是創建類的代碼(這段代碼可以是一個函數,也可以是一個類);如果這段代碼是類,"__metaclass__"的類名總是以Metaclass結尾,以便清楚地表示這是一個元類。
對於元類的查找,Python有一套規則:
- Python解釋器會在當前類中查找"__metaclass__"屬性對於的代碼,然后創建一個類對象
- 如果沒有找到"__metaclass__"屬性,會繼續在父類中尋找"__metaclass__屬性",並嘗試前面同樣的操作
- 如果在任何父類中都找不到"__metaclass__",就會用內置的type來創建這個類對象
def queueMeta(name, bases, attrs): attrs['InQueue'] = lambda self, value: self.append(value) def deQueue(self): if len(self) > 0: return self.pop(0) attrs['DeQueue'] = deQueue # 直接調用type內建函數 return type(name, bases, attrs) # 元類從`type`類型派生 class QueueMetaclass(type): def __new__(cls, name, bases, attrs): attrs['InQueue'] = lambda self, value: self.append(value) def deQueue(self): if len(self) > 0: return self.pop(0) attrs['DeQueue'] = deQueue # 直接調用type內建函數 # return type(name, bases, attrs) # 通過父類的__new__方法 return type.__new__(cls, name, bases, attrs) class MyQueue(list): # 設置metaclass屬性,可以使用一個函數,也可以使用一個類,只要是可以創建類的代碼 #__metaclass__ = queueMeta __metaclass__ = QueueMetaclass pass q = MyQueue("hello World") print q q.InQueue("!") print q q.DeQueue() print q
代碼中的MyQueue類型繼承自list,但是通過設置"__metaclass__"屬性,可以修改創建的類。也就是說,元類做了下面的事情:
- 攔截類的創建
- 根據"__metaclass__"對應的代碼修改類
- 返回修改之后的類
元類的__init__和__new__
當創建元類的時候,為了定制創建出來的類的特性,一般會實現元類的"__init__"和"__new__"方法。
class MyMetaclass(type): def __new__(meta, name, bases, attrs): print '-----------------------------------' print "Allocating memory for class", name print meta print bases print attrs return super(MyMetaclass, meta).__new__(meta, name, bases, attrs) def __init__(cls, name, bases, attrs): print '-----------------------------------' print "Initializing class", name print cls print bases print attrs super(MyMetaclass, cls).__init__(name, bases, attrs) class MyClass(object): __metaclass__ = MyMetaclass def foo(self, param): pass barattr = 2
通過這個例子演示了使用元類的"__init__"和"__new__"方法:
元類的__call__
"__call__"是另外一個經常在實現元類時被重寫的方法,與"__init__"和"__new__"不同的是,當調用"__call__"的時候,類已經被創建出來了,"__call__"是作用在類創建的實例過程。
看下面的代碼:
class MyMetaclass(type): def __call__(cls, *args, **kwds): print '__call__ of ', str(cls) print '__call__ *args=', str(args) return type.__call__(cls, *args, **kwds) class MyClass(object): __metaclass__ = MyMetaclass def __init__(self, a, b): print 'MyClass object with a=%s, b=%s' % (a, b) print 'gonna create foo now...' foo = MyClass(1, 2)
代碼的輸出為:
元類使用舉例
前面已經介紹了很多關於元類的知識了,下面看看怎么實際使用元類。
元類在ORM中是比較常用的,因為需要在運行時創建類型,看下面簡單的例子:
class Field(object): def __init__(self, fname, ftype): self.fname = fname self.ftype = ftype def __str__(self): return '{%s: (%s, %s)}' % (self.__class__.__name__, self.fname, self.ftype) class StringField(Field): def __init__(self, fname): super(StringField, self).__init__(fname, 'varchar(100)') class IntegerField(Field): def __init__(self, fname): super(IntegerField, self).__init__(fname, 'bigint') class ModelMetaclass(type): def __new__(cls, name, bases, attrs): if name == "Model": return super(ModelMetaclass, cls).__new__(cls, name, bases, attrs) else: mapping = {} print "Create Model for:", name for k, v in attrs.items(): if isinstance(v, Field): print "mapping %s with %s" %(k, v) mapping[k] = v attrs['_table'] = name attrs['_mapping'] = mapping return type.__new__(cls, name, bases, attrs) class Model(dict): __metaclass__ = ModelMetaclass def __init__(self, **kwargs): for key in kwargs.keys(): if key not in self.__class__._mapping.keys(): print "Key '%s' is not defined for %s" %(key, self.__class__.__name__) return super(Model, self).__init__(**kwargs) def save(self): fields = [] params = [] args = [] for k, v in self.__class__._mapping.items(): fields.append(k) params.append("'{0}'".format(self[k])) sql = 'insert into %s (%s) values (%s)' % (self.__class__._table, ','.join(fields), ','.join(params)) print 'SQL: %s' %sql class Student(Model): id = IntegerField('id_c') name = StringField('username_c') email = StringField('email_c') print "-------------------------------------------------" print Student._table print Student._mapping print "-------------------------------------------------" s1 = Student(id = 1, name = "Wilber", email = "wilber@sh.com") s1.save() print "-------------------------------------------------" s2 = Student(id = 1, name = "Wilber", gender = "male")
代碼中通過元類創建Student類,並將類的屬性與數據表關聯起來:
總結
本文介紹了Python中元類的概念,通過元類可以在運行時創建類。
當用戶定義一個類class的時候,Python解釋器就會在當前類中查找"__metaclass__"屬性,如果找到,就通過該屬性對應的代碼創建類;如果沒有找到,就繼續以相同的規則查找父類。如果在任何父類中都找不到"__metaclass__",就會用內置的type來創建類對象。