1. 程式人生 > 實用技巧 >廖雪峰Python教程中簡單ORM程式碼解讀

廖雪峰Python教程中簡單ORM程式碼解讀

程式碼

# -*- coding: utf-8 -*-

' Simple ORM using metaclass '

class Field(object):

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

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)

class StringField(Field):

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

class IntegerField(Field):

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

class ModelMetaclass(type):
    # 元類實現了對model類的定製, 通過User類的繼承請求中的引數, 來實現對Model這個類的定製
    # 下面的User(xxx), 經過元類的定製後, 用來建立User的類增加了一個__mapping__字典和___table__屬性
    def __new__(cls, name, bases, attrs):
        if name=='Model':
            # 這裡是為了排除掉對Model類的修改, 試想一下當因為建立了一個User物件然後建立了一個帶有屬性的Model類,
            # 然後又因為某些原因想要單獨建立一個Model類的時候, 如果沒有這一步會創造出一個帶有部分屬性的Model
            # 總之就是排除了在某個子類呼叫Model時候造成的修改
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        mappings = dict()
        # 在這裡列印了之後會發現每個都是field
        print(attrs)
        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)

class Model(dict, metaclass=ModelMetaclass):
    # Model是以dict為父類, 所以通過傳入的引數確定dict的內容
    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('?')
            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))

# testing code:

class User(Model):
    # 這裡的幾個id, name, email和password都是類變數
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 這裡面的鍵值對都是用來初始化User這個dict的
u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
print(u.id)
u.save()

解讀

建立一個新的User步驟

  1. 首先在建立一個User物件的時候, 先去找了User的父類Model
  2. 找到了Model並且將要載入這個類的時候, 發現Model是定義了元類的, 所以接下來要去找元類
  3. 在元類的__new__方法中, 由於這個呼叫是從User發起的, 所以帶進來的引數都是User裡面的引數
    • 在元類中如果列印了attrs會發現都是field的子類, 可能有人會有疑惑為什麼不是User括號中的字串或者是整數
    • 原因是我們目前還處在定製Model類的過程中, 而User括號裡面的是用來初始化物件的引數
    • 所以說用來初始化類的引數當然就是User類中的引數了
  4. 判斷是定製Model類還是要初始化一個Model類
  5. 根據傳進來的引數(User的類變數), 給定製的Model類新增一些屬性
  6. 建立定製好的Model類
  7. 建立User物件, 並且用傳進去的引數初始化這個魔改版dict