python中元類的理解

python中的metaclass就是元類,當我們定義了類以後,就可以根據這個類創建出實例,所以:先定義類,然後創建實例。

但是如果我們想創建出類呢?那就必須根據metaclass創建出類,所以:先定義metaclass,然後創建類。

連接起來就是:先定義metaclass,就可以創建類,最後創建實例。

所以,metaclass允許你創建類或者修改類。換句話說,你可以把類看成是metaclass創建出來的“實例”。

我們先看一個簡單的例子,這個metaclass可以給我們自定義的MyList增加一個add方法:

定義ListMetaclass,按照默認習慣,metaclass的類名總是以Metaclass結尾,以便清楚地表示這是一個metaclass:

# metaclass是類的模板,所以必須從`type`類型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)
#有了ListMetaclass,我們在定義類的時候還要指示使用ListMetaclass來定製類,傳入關鍵字參數metaclass:

class MyList(list, metaclass=ListMetaclass):
    pass

當我們傳入關鍵字參數metaclass時,魔術就生效了,它指示Python解釋器在創建MyList時,要通過ListMetaclass.new()來創建,在此,我們可以修改類的定義,比如,加上新的方法,然後,返回修改後的定義。

new()方法接收到的參數依次是:

  • 當前準備創建的類的對象;
  • 類的名字;
  • 類繼承的父類集合
  • 類的方法集合

測試一下MyList是否可以調用add()方法:

>>> L = MyList()
>>> L.add(1)
>> L
[1]

而普通的list沒有add()方法:

>>> L2 = list()
>>> L2.add(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'add'

下面來理解一下一個簡單的ORM框架

#Field規定了數據庫中每個字段的名稱和類型
class Field(object):

    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):

#StringField中定義了具體的String的類型
class StringField(Field):

    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

#IntegerField中定義了具體的Integer的類型
class IntegerField(Field):

    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')

#爲Model的元類
class ModelMetaclass(type):
    #arrts爲具體實現類的屬性,這裏的做法是取除實現類中類型爲Field的屬性,將其映射爲實現類中的__mappings__屬性,並取類名將其映射爲實現類中的__table__屬性
    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings # 保存屬性和列的映射關係
        attrs['__table__'] = name # 假設表名和類名一致
        return type.__new__(cls, name, bases, attrs)

#Model繼承元類ModelMetaclass,當一個類繼承這個類時,會自動去執行這個ModelMetaclass中的__new__方法,在這裏實現了遍歷由元類中添加的__mappings__屬性,生成SQL,並插入__table__中。
#其接受命名參數,並從__mappings__中拿到key去屬性中獲取值
class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            #通過__mappings__去自身拿取key爲其中設置的鍵,也就是我們在User中定義的4個屬性的名稱
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

#該類繼承Model
class User(Model):
    # 定義類的屬性到列的映射,用於元類獲取__mappings__
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 創建一個實例:
u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
# 保存到數據庫:
u.save()

輸出的結果如下:

Found model: User
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
SQL: insert into User (password,email,username,id) values (?,?,?,?)
ARGS: ['my-pwd', '[email protected]', 'Michael', 12345]

參考鏈接:廖雪峯的官方網站

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章