Python中的元類


從前面"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有一套規則:

  1. Python解釋器會在當前類中查找"__metaclass__"屬性對於的代碼,然后創建一個類對象
  2. 如果沒有找到"__metaclass__"屬性,會繼續在父類中尋找"__metaclass__屬性",並嘗試前面同樣的操作
  3. 如果在任何父類中都找不到"__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__"屬性,可以修改創建的類。也就是說,元類做了下面的事情:

  1. 攔截類的創建
  2. 根據"__metaclass__"對應的代碼修改類
  3. 返回修改之后的類

元類的__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來創建類對象。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM