orm框架分析——仿优酷项目

一、ORM介绍

对象关系映射,把数据库中的表数据(表名、表记录、字段)全部映射到python中。

​ mysql:            python:

​ 表名 ————>类名

​ 记录 ————>对象

​ 字段 ————>对象.属性

第一步:写字段类型的类

    --字段名
​   --字段类型(长度)
​       --varchar(256)
​       --int
​   --是否为主键
​   --默认值

​   仿优酷中使用的字段类型:
​       —int
​       —string

第二步:写表类

        User:  #用户表
​               user_name
​               pwd

​       Movie:  #电影表
​               movie_name
​               movie_size

​       Notice: #公告表
​               title
​               content

接下来我们解决三个问题:

问题1: 假设100张表就需要写100个__init__。
解决:
    继承一个Models父类

问题2: 每张表的字段名与字段数量不同,导致无法直接继承Models父类
解决:
    dict是对象,继承dict,触发字典内部的__init__(可接受任意数量以及任意类型属性)

问题3: 字典的取/存值方式有限,希望改为 对象.属性取值, 对象.属性=值 存值的方式。
解决:
    __getattr__: 取值方式
    __setattr__: 存值方式

第三步:表的约束

问题: 让所有的表类都遵循以下约束
        - 表名
        - 必须要有一个唯一主键
        - 表的字段

解决:通过元类去控制类的创建,使其遵循以上约束.

第四步:

 - 表名
 - 必须要有一个唯一主键
 - 表的字段
***************************mysql_control.py******************************
import pymysql

class MySQL:
    #接下来是一个单例模式,因为每定义一个函数都要实例化mysql_control.MySQL()   
    __instance = None   #隐藏

    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = object.__new__(cls)
        return cls.__instance

    def __init__(self):
        #创建数据库连接
        self.mysql = pymysql.connect(
            host = '127.0.0.1',
            port = 3306,
            user= 'root',
            password = '12345678',
            database = 'yjy',
            charset ='utf8',
            autocommit = True
        )

        #获取游标
        self.cursor = self.mysql.cursor(
            pymysql.cursors.DictCursor
        )


    #查看
    def select(self,sql,args=None):
        self.cursor.execute(sql,args)   #提交select语句

        #获取查询结果
        res = self.cursor.fetchall()
        return res

    #提交
    def execute(self,sql,args):
        try:
            # insert into table(name, pwd) values('yjy', '123');
            self.cursor.execute(sql,args)

        except Exception as e:
            print(e)

    def close(self):
        self.cursor.close()  #关闭游标
        self.mysql.close()  #关闭连接对象mysql
***************************orm.py******************************
'''
需求:
    创建一张User表,表内字段(id,name,pwd)
'''
# class User:   #先定义一个User类,即创建User表,orm的特性就是将表名映射成类名
#     def __init__(self,id,name,password):
#         self.id = id
#         self.name = name
#         self.password = password

import mysql_control
'''第一步:写字段类型类'''
#父类
# 定义这个父类的关键就在于把各个类中相同的东西拿出来,减小代码冗余
class Field:
    def __init__(self,name,column_type,primary_key,default):
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default

# int类型
class IntegerField(Field):  #继承父类
    def __init__(self,name,column_type= "int",primary_key=False,default=0):
        super().__init__(name,column_type,primary_key,default)  #继承父类中的这些方法

#string varchar()  类型
class StringField(Field):
    def __init__(self,name,column_type= "varchar(256)",primary_key= False,default = None):
        super().__init__(name,column_type,primary_key,default)


'''第三步,第四步:通过元类进行表的约束'''

class OrmMetaClass(type):
    def __new__(cls, class_name, class_base, class_attr):
        # 接收Models类的东西、User类的东西
        # print(class_name, class_base, class_attr)  # (类名、父类、类的名称空间)
        # print(class_attr)

        if class_name == "Models":
            return type.__new__(cls, class_name, class_base, class_attr)

        # 若不是Models则开始控制类的创建
        # 1.表名  若table_name没有,则取class_name
        table_name = class_attr.get('table_name', class_name)  # 表的名字

        mappings = {}  # 在python中返回的表,它是以;列表套字典的形式

        primary_key = None

        # 2.约束必须有一个唯一的主键
        for k, v in class_attr.items():
            # print(k, v)  # user_id, IntegerField(name='user_id', primary_key=True)
            if isinstance(v, Field):
                # 3.获取所有字段,把所有表的字段都封装到一个独立的字典中mappings
                mappings[k] = v

                if v.primary_key:  # 如果字段有主键

                    # 判断是否有多个主键
                    if primary_key:
                        raise TypeError('只能有一个主键')  # 给他跑一个异常

                    primary_key = v.name
        # 剔除原表类的名称空间中重复的字段属性,目的是为了减少内存的占用
        for k in mappings.keys():
            class_attr.pop(k)
            # 如果没有主键
            if not primary_key:
                raise TypeError('必须要有一个主键')

        class_attr['table_name'] = table_name
        # select * from self.table_name where id=self.primary_key。
        class_attr['primary_key'] = primary_key
        # insert into self.table_name(字段名, ...) values (字段对象.值)
        class_attr['mappings'] = mappings  # {'字段名': '字段对象'}
        # print(class_attr)
        return type.__new__(cls, class_name, class_base, class_attr)


# 解决100张表就需要写100个__init__的问题
class Models(dict, metaclass=OrmMetaClass):
    # 继承dict解决的是每张表的字段名与字段数量不同,导致无法直接继承Models父类
    # 继承dict,触发字典内部的__init__(可接受任意数量以及任意类型属性
    def __init__(self, **kwargs):  # **kwargs就是id,name,pwd   'name': 'yjy' --- >  name=yjy
        super().__init__(**kwargs)

    def __getattr__(self, item):
        # 将 字典[key]的取值方式转换为---> 字典.key的取值方式
        return self.get(item)

    def __setattr__(self, key, value):
        # 将字典的赋值方式转变为对象.属性的方式
        self[key] = value

    # 查看
    @classmethod
    def select(cls, **kwargs):  # name= 'yjy',pwd= '123'
        mysql_obj = mysql_control.MySQL()
        if not kwargs:
            # 拼接select查询语句
            sql = 'select * from %s' % cls.table_name

            res = mysql_obj.select(sql)

        # sql(查询条件):select * from User where name = yjy;
        else:
            key = list(kwargs.keys())[0]  # data_name
            value = kwargs.get(key)  # yjy
            # 拼接select查询语句
            sql = 'select * from %s where %s =?' % (cls.table_name, key)
            # 防止sql注入
            sql = sql.replace('?', '%s')
            # 提交sql,并返回查询结果
            res = mysql_obj.select(sql, value)

        # cls(**{key:value}) ---> cls(key=value) --> obj

        # 把列表套字典 ---> 列表套对象
        return [cls(**r) for r in res]  # [{}, {}, {}] ---> [obj, obj, obj..]

    # 插入
    def save(self):
        mysql = mysql_control.MySQL()
        # sql: insert into table(name, pwd..) values('yjy', '123', ...);
        # sql: insert into table(name, pwd..) values(?,?,?);

        # 获取所有的字段名
        fields = []  # 他是一个列表,我们需要用','.join() 拼接起来

        # 获取字段对应的值
        values = []

        # 替换条件
        replace = []  # [?,?]
        for k, v in self.mappings.items():
            # print(k, v)  # user_id = IntegerField(name='user_id', primary_key=True)
            fields.append(k)  # k == v.name
            values.append(getattr(self, v.name, v.default))  # getattr(User(), v.name) --> 字段值
            replace.append('?')  # [?, ?, ?...]

        # insert into user_info(user_id,user_name,pwd) values(?,?,?)
        sql = 'insert into %s(%s) values(%s)' % (self.table_name, ','.join(fields), ','.join(replace))
        sql = sql.replace('?', '%s')  # 防止sql注入
        mysql.execute(sql, values)  # 提交sql语句

    # 更新
    def sql_update(self):
        '''默认使用主键当做更新的查询条件'''
        mysql = mysql_control.MySQL()

        # 获取字段名
        fields = []

        # 获取字段名
        values = []

        # 获取主键的值
        primary_key = None

        # name=yjy, pwd='123'
        for k, v in self.mappings.items():
            if v.primary_key:
                # 通过反射获取主键的值
                primary_key = getattr(self, v.name)

            else:
                fields.append(v.name + '=?')  # [name=?, pwd=?]
                # 通过反射获取修改字段的值
                values.append(
                    getattr(self, v.name)
                )

        # sql: update user_info set name=yjy, pwd='123' where user_id=1;
        # sql: update user_info set name=?, pwd=? where user_id=1;
        sql = 'update %s set %s where %s=%s' % (self.table_name, ','.join(fields), self.primary_key, primary_key)
        sql = sql.replace('?', '%s')
        mysql.execute(sql, values)

'''第二步:创建表类'''
class User(Models):  #Models中已经有表的字段,所以在这里直接继承就好了
    # 自定义表名
    table_name = 'user_info'  # {'table_name': 'user_info'}
    # id, name, pwd
    user_id = IntegerField(name='user_id',primary_key=True)
    user_name = StringField(name='user_name')
    pwd = StringField(name='pwd')

#注意:我们要在数据库中创建下面这样一张表
'''
create table user_info(
user_id int primary key auto_increment,
user_name varchar(256),
pwd varchar(256));
'''

class Movie(Models):
    pass

class Notice(Models):
    pass



if __name__ == '__main__':
    obj = User(user_name='yjy')
    obj.save()  #插入数据

    user_obj = User.select(user_name='yjy')[0]  #查看数据数据
    print(user_obj)  ##{'user_id': 1, 'user_name': 'yjy', 'pwd': None}
    print(user_obj.user_name)   #yjy

    user_obj.user_name = 'yjyyjyyjy'
    user_obj.sql_update()
    print(user_obj.user_name)    #yjyyjyyjy
    
#数据库中结果
+---------+-----------+------+
| user_id | user_name | pwd  |
+---------+-----------+------+
|       1 | yjyyjyyjy | NULL |
+---------+-----------+------+
原文地址:https://www.cnblogs.com/lulingjie/p/11656093.html