Python 高級編程和異步IO併發編程 --08_7 通過元類實現orm

# 需求: 定義一個class,
# orm系統:將類映射到數據庫的一張表,對類操作,就可以將數據寫入表中,這樣就可以脫離SQL語句。
# 爲了實現屬性賦值報錯的功能,就需要用到前面提到的屬性描述符。
import numbers

class Field:
    pass

class IntField:
    # 數據描述符
    # 此處的類如何變成屬性描述符?只需要實現get方法,或者是set方法,類裏面的任一個方法 都是屬性描述符,可以用於後續規則的檢查
    # IntField實現對Int類型的檢查
    def __init__(self,db_column,min_value=None,max_value=None):  # 初始化,涉及到min_vlaue與max_value兩個參數,可以默認不設置,均爲None
        self._value = None # 此處爲什麼用self._value? 屬性描述符保存值的時候,是保存在value中的。
        # 用單下劃線_value,指明這是一個內部的變量,不希望通過self.value來訪問,這是一種規範,非必須。
        self.min_value = min_value
        self.max_value = max_value
        self.db_column = db_column
        if min_value is not None:
            if isinstance(min_value,numbers.Integral): # 判斷,如果有數據傳入,是否傳入的爲整型?
                raise ValueError("min_value must be int")
            elif min_value < 0:  # 用於判斷,當輸入必須爲正整數的情形
                raise ValueError("min_value must be a postive int")
        if max_value is not None:
            if isinstance(max_value,numbers.Integral): # 判斷,如果有數據傳入,是否傳入的爲整型?
                raise ValueError("max_value must be int")
            elif max_value < 0:  # 用於判斷,當輸入必須爲正整數的情形
                raise ValueError("max_value must be a postive int")
        if min_value is not None and max_value is not None:
            if min_value > max_value:
                raise ValueError("min_value must be smaller than max_value")

    def __get__(self,instance,owner):  #
        return self._value   # __set__中定義了value參數,此處返回,需要確保前後一致,因此爲self._value

    def __set__(self,instance,value):  # 賦值/set的時候,進行了參數類型的檢查
        if not isinstance(value,numbers.Integral):
            raise ValueError("int value required")
        if value < self.min_value or value > self.max_value:
            raise ValueError("value must betwen min_value and max_value")
        self._value = value   # 將傳進來的value賦給IntFiled這個class中

class CharField:
    def __init__(self,db_column,max_length=None):
        self._value = None
        self.db_column = db_column
        self.max_length = max_length
        if max_length is None:
            raise ValueError("You must specify max_lenth for charfield")

    def __get__(self,instance,owner):
        return self._value

    def __set__(self,instance,value):
        if not isinstance(value,str):
            raise ValueError("string value required")
        if len(value) > self.max_length:
            raise ValueError("value length execess length of max_length")
        self._value = value   # 將傳進來的value賦給IntFiled這個class中

class ModelMetaClass(type):
    def __new__(cls,name,bases,attrs,**kwargs): # 此處,*args: tuple自動會拆包,分別賦值給name,基類,屬性中。
        if name == "BaseModel":
            return super().__new__(cls,name,bases,attrs,**kwargs)
        fields = {}
        for key,value in attrs.items():
            if isinstance(value,Field):
                fields[key] = value
        attrs_meta = attrs.get("Meta",None)
        _meta = {}
        db_table = name.lower()
        if attrs_meta is not None:
            table = getattr(attrs_meta,"db_table",None)
            if table is not None:
                db_table = table
        _meta["db_table"] = db_table
        attrs["_meta"] = _meta
        attrs["fields"] = fields
        del attrs["meta"]
        return super().__new__(cls,name,bases,attrs,**kwargs)  # __new__方法一定要return一個類

class BaseModel(metaclas = ModelMetaClass):
    def __init__(self,*args,**kwargs):
        for key,value in kwargs.items():
            setattr(self,key,value)
        return super().__init__()

    def save(self):
        fields = []
        values = []
        for key,value in self.fields.items():
            db_column = value.db_column
            if db_column == None:
                db_column = key.lower()
            fields.append(db_column)
            value = getattr(self,key)
            values.append(str(value))
        sql = "insert {db_table}({fields})user(name,age) value({values})".format(db_table = self._meta["db_table"],fields = ",".join(fields),values=",".joint(values))

class User(BaseModel): # 希望在創建User時,可以添加很多自己的屬性註冊到User的屬性中來,將元類寫的複雜一定,後續的類創建就會省很多代碼。

    name = CharField(db_column="",max_length = 10) # db_column爲數據庫中所對應的列,max_length定義數據庫中最大長度
    age = IntField(db_column="",min_value=1,max_value=100)# db_column爲數據庫中所對應的字段,max_length定義數據庫中最大長度

    class Meta: # 定義表的列
        db_table = "user"  # 定義數據表的名稱,如果此處不賦值,則默認會用外面的Class,即User的小寫user進行賦值。

if __name__=="__main__":
    user = User()
    user.name="Tom"
    user.age=18
    user.save()

1.ORM是什麼

 ORM是python編程語言後端web框架Django的核心思想,“Object Relational Mapping”,即對象-關係映射,簡稱ORM。

一句話理解就是:創建一個實例對象,用創建它的類名當作數據庫表名,用創建他的類屬性對應數據表的字段,當對這個實例對象操作時,能夠對應MySQL語句

demo:

class User(父類省略):
    uid = ('uid', "int unsigned")
    name = ('username', "varchar(30)")
    email = ('email', "varchar(30)")
    password = ('password', "varchar(30)")
    ...省略...

u = User(uid=12345, name='Michael', email='[email protected]', password='my-pwd')
u.save()

# 對應如下sql語句

# insert into User (username,email,password,uid)

# values ('Michael','[email protected]','my-pwd',12345)

說明

  1. 所謂的ORM就是讓開發者在操作數據庫的時候,能夠像操作對象時通過xxxx.屬性=yyyy一樣簡單,這是開發ORM的初衷
  2. 只不過ORM的實現較爲複雜,Django中已經實現了 很複雜的操作,本節知識 主要通過完成一個 insert相類似的ORM,理解其中的道理就就可以了

2. 通過元類簡單實現ORM中的insert功能

class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        mappings = dict()
        # 判斷是否需要保存
        for k, v in attrs.items():
            # 判斷是否是元組類型
            if isinstance(v, tuple):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v

            # 刪除這些已經在字典中存儲的屬性
        for k in mappings.keys():
            attrs.pop(k)

        # 將之前的uid/name/email/password以及對應的對象引用、類名字
        attrs['__mappings__'] = mappings  # 保存屬性和列的映射關係
        attrs['__table__'] = name  # 假設表名和類名一致
        return type.__new__(cls, name, bases, attrs)

class User(metaclass=ModelMetaclass):
    uid = ('uid', "int unsigned")
    name = ('username', "varchar(30)")
    email = ('email', "varchar(30)")
    password = ('password', "varchar(30)")
    # 當指定元類之後,以上的類屬性將不在類中,而是在__mappings__屬性指定的字典中存儲
    # 以上User類中有
    # __mappings__ = {
    # "uid": ('uid', "int unsigned")
    # "name": ('username', "varchar(30)")
    # "email": ('email', "varchar(30)")
    # "password": ('password', "varchar(30)")
    # }
    # __table__ = "User"

    def __init__(self, **kwargs):
        for name, value in kwargs.items():
            setattr(self, name, value)
    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v[0])
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join([str(i) for i in args]))
        print('SQL: %s' % sql)

u = User(uid=12345, name='Michael', email='[email protected]', password='my-pwd')
# print(u.__dict__)
u.save()

 

執行的效果:

Found mapping: uid ==> ('uid', 'int unsigned')
Found mapping: name ==> ('username', 'varchar(30)')
Found mapping: email ==> ('email', 'varchar(30)')
Found mapping: password ==> ('password', 'varchar(30)')
SQL: insert into User (uid,username,email,password) values (12345,Michael,[email protected],my-pwd)

3. 完善對數據類型的檢測 

class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        mappings = dict()
        # 判斷是否需要保存
        for k, v in attrs.items():
            # 判斷是否是元組類型
            if isinstance(v, tuple):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v

        # 刪除這些已經在字典中存儲的屬性
        for k in mappings.keys():
            attrs.pop(k)

        # 將之前的uid/name/email/password以及對應的對象引用、類名字
        attrs['__mappings__'] = mappings  # 保存屬性和列的映射關係
        attrs['__table__'] = name  # 假設表名和類名一致
        return type.__new__(cls, name, bases, attrs)


class User(metaclass=ModelMetaclass):
    uid = ('uid', "int unsigned")
    name = ('username', "varchar(30)")
    email = ('email', "varchar(30)")
    password = ('password', "varchar(30)")

    # 當指定元類之後,以上的類屬性將不在類中,而是在__mappings__屬性指定的字典中存儲
    # 以上User類中有
    # __mappings__ = {
    #     "uid": ('uid', "int unsigned")
    #     "name": ('username', "varchar(30)")
    #     "email": ('email', "varchar(30)")
    #     "password": ('password', "varchar(30)")
    # }
    # __table__ = "User"
    def __init__(self, **kwargs):
        for name, value in kwargs.items():
            setattr(self, name, value)

    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v[0])
            args.append(getattr(self, k, None))

        args_temp = list()
        for temp in args:
            if isinstance(temp, int):
                # 判斷如果是數字類型
                args_temp.append(str(temp))
            elif isinstance(temp, str):
                # 判斷如果是字符串類型
                args_temp.append("""'%s'""" % temp)
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
        print('SQL: %s' % sql)


u = User(uid=12345, name='Michael', email='[email protected]', password='my-pwd')
# print(u.__dict__)
u.save()

 

運行效果如下:

Found mapping: uid ==> ('uid', 'int unsigned')
Found mapping: name ==> ('username', 'varchar(30)')
Found mapping: email ==> ('email', 'varchar(30)')
Found mapping: password ==> ('password', 'varchar(30)')
SQL: insert into User (uid,username,email,password) values (12345,'Michael','[email protected]','my-pwd')

4. 抽取到基類中

class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        mappings = dict()
        # 判斷是否需要保存
        for k, v in attrs.items():
            # 判斷是否是元組類型
            if isinstance(v, tuple):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v

        # 刪除這些已經在字典中存儲的屬性
        for k in mappings.keys():
            attrs.pop(k)

        # 將之前的uid/name/email/password以及對應的對象引用、類名字
        attrs['__mappings__'] = mappings  # 保存屬性和列的映射關係
        attrs['__table__'] = name  # 假設表名和類名一致
        return type.__new__(cls, name, bases, attrs)


class Model(object, metaclass=ModelMetaclass):
    def __init__(self, **kwargs):
        for name, value in kwargs.items():
            setattr(self, name, value)

    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v[0])
            args.append(getattr(self, k, None))

        args_temp = list()
        for temp in args:
            # 判斷入如果是數字類型
            if isinstance(temp, int):
                args_temp.append(str(temp))
            elif isinstance(temp, str):
                args_temp.append("""'%s'""" % temp)
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
        print('SQL: %s' % sql)


class User(Model):
    uid = ('uid', "int unsigned")
    name = ('username', "varchar(30)")
    email = ('email', "varchar(30)")
    password = ('password', "varchar(30)")


u = User(uid=12345, name='Michael', email='[email protected]', password='my-pwd')
# print(u.__dict__)
u.save()
#!/user/bin/env python
# -*- coding:utf-8 -*-
# 需求
import numbers


class Field:
    pass


class IntField(Field):
    def __init__(self, db_column, min_value=None, max_value=None):
        self._value = None
        self.db_column = db_column
        self.min_value = min_value
        self.max_value = max_value
        if min_value is not None:
            if not isinstance(min_value, numbers.Integral):
                raise ValueError('min_value must be int')
            elif min_value < 0:
                raise ValueError('min_value must be positive int')
        if max_value is not None:
            if not isinstance(max_value, numbers.Integral):
                raise ValueError('max_value must be int')
            elif min_value < 0:
                raise ValueError('max_value must be positive int')
        if min_value is not None and max_value is not None:
            if min_value > max_value:
                raise ValueError('min_value must be smaller than max_value')

    def __get__(self, instance, owner):
        return self._value

    def __set__(self, instance, value):
        if not isinstance(value, numbers.Integral):
            raise ValueError('int value need')
        if value < self.min_value or value > self.max_value:
            raise ValueError('value must between min_value and max_value')
        self._value = value


class CharField(Field):
    def __init__(self, db_column, max_length=None):
        self._value = None
        self.db_column = db_column
        if max_length is None:
            raise ValueError('you must spcify max_length for CharField')
        self.max_length = max_length

    def __get__(self, instance, owner):
        return self._value

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError('string value need')

        if len(value) > self.max_length:
            raise ValueError('value len excess len of max_length')
        self._value = value


class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs, **kwargs):
        if name == 'BaseModel':
            return super().__new__(cls, name, bases, attrs, **kwargs)
        fields = {}
        for key, value in attrs.items():
            if isinstance(value, Field):
                fields[key] = value
        attrs_meta = attrs.get('Meta', None)
        _meta = {}
        db_table = name.lower()
        if attrs_meta is not None:
            table = getattr(attrs_meta, 'db_table', None)
            if table is None:
                db_table = table
        _meta['db_table'] = db_table
        attrs['_meta'] = _meta
        attrs['fields'] = fields
        del attrs['Meta']
        return super().__new__(cls, name, bases, attrs, **kwargs)


class BaseModel(metaclass=ModelMetaClass):
    def __init__(self, *args, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
        return super().__init__()

    def save(self):
        fields = []
        values = []
        for key, value in self.fields.items():
            db_column = value.db_column
            if db_column is None:
                db_column = key.lower()
            fields.append(db_column)
            value = getattr(self, key)
            values.append(str(value))

        sql = 'insert {db_table}({fields}) value({values})'.format(db_table=self._meta['db_table'], fields=','.join(fields), values=','.join(values))
        print(sql)


class User(BaseModel):
    name = CharField(db_column='name', max_length=10)
    age = IntField(db_column='age', min_value=0, max_value=100)

    class Meta:
        db_table = 'user'


if __name__ == '__main__':
    user = User()
    user.name = 'zy'
    user.age = 21
    user.save()
insert user(name,age) value(zy,21)

 

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