1107 python自定义实现ORM

ORM

存: 从代码通过ORM保存到mysql中,保存的结果是json

取: 从mysql中通过ORM取出,取出来的也是json

对象关系映射,

客户端的请求到服务端,服务端接受到指令,使用ORM对数据库进行操作,而数据库的库表都是已经存在的,ORM中操作的指令无非是对应数据库的表字段,通过操作ORM的代码来实现对表,表中字段数据的操作

一开始写的时候就是对象,然后通过ORM保存到数据库中,保存的都是json数据,但想要取出来的也是对象,所以使用元类来进行封装创建

接口层 -- 数据层 -- ORM -- 数据库

			对象关系映射
	类名		  ---		表名
	
	对象	       ---       数据行
		# 对象 通过类实例化传参得来
		
	对象.属性	  ---		字段	
		# 对象.属性 在类实例化的时候添加字段的属性,触发__init__
创建一个User表,表内有字段(id,name,pwd)
# 1.表名就是类名User
# 2.字段:需要在类实例化的时候添加字段(.属性)
#     实例化会触发__init__方法
#     2.1 创建字段时具有不同的类型,创建字段类型类
#             - 字段名
#             - 字段类型
#                 int
#                 varchar
#             - 是否为主键
#             - 默认值
#             定义字段类型类,继承同一父类

模型表类

创建字段类型类

# 写字段类型类
定义字段的约束:字段的名字,字段的类型,字段是否为主键,字段有无默认值
# 字段父类
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__(self,name,column_type,primary_key,default)
# varchar
class StringFiled(Field):
    def __init__(self,name,column_type = "varchar(255)",primary_key = False,default =None):
        super().__init__(self,name,column_type,primary_key,default)

创建模型表类

模型表类是一张一张的表,表中有username,pwd等字段用于记录用户的数据

user表,movie表
user表中应该有 username,pwd	--- 这是字段
# 创建模型表类
#     1.创建表类的重复 --- 继承
#     2.每一张表的字段数量,名字都不一样,无法继承同一父类
#         --- 父类继承dict,dict是对象,继承dict,触发内部__init__(可接受任意数量已经类型属性)
#         dict = {key:value}    与  dict(key=value)  结果一致
#     3.字典的取存值方式有限,改为对象.属性的方式存取值
#         存, 对象.属性没有时触发    __getattr__
#         取, 对象.属性调用时触发    __setattr__
class Models(dict):
    # 1.使用字典接受所有的关键字参数
    def __init__(self,**kwargs):
        super().__init__(**kwargs)  # kwargs接受所有的关键字参数
    # 2.取值时使用getattr,self是调用者本身item是没有的属性(key)
    def __getattr__(self, item):
        return self.get(item)
    # 3.存值时使用setattr,将调用者本身的字典中的key赋值保存为value
    def __setattr__(self, key, value):
        self[key] = value



class User(Models):
    # def __init__(self,id,name,pwd):
    #     self.id = id
    #     self.name = name
    #     self.pwd = pwd
    pass

class Movie(Models):
    # def __init__(self,id,name,pwd):
    #     self.id = id
    #     self.name = name
    #     self.pwd = pwd
    pass


if __name__ == '__main__':
    d1=dict(name = 1)   # {'name': 1}
    d2 = User(name='222')   #{'name': '222'}
    print(d1)
    # print(d2.name)    # 报错
    # 取值
    print(d2['name'])   # 222
    print(d2.name)      # 222
    # 存值
    d2['name']=1231
    print(d2)       #{'name': 1231}
    d2.pwd = 1231
    print(d2)       #{'name': 1231, 'pwd': 1231}

表的约束

// 问题:让所有的表类都遵循约束,防止创建传值的失误
	- 表名
	- 必须有唯一主键
	- 表的字段
解决:	使用元类去控制类的创建,使其遵循规则
	元类控制:
        - 表名
        - 必须有唯一主键
        - 表的字段

元类的封装

# 写字段类型类

# 字段父类
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)
# varchar
class StringFiled(Field):
    def __init__(self,name,column_type = "varchar(255)",primary_key = False,default=None):
        super().__init__(name,column_type,primary_key,default)


# 4.元类的创建
class MyMetaclass(type):
    # 子类的方法覆盖父类的__init方法
    # 控制了类的定义
    def __new__(cls,class_name,class_base,class_dict):
        # 接收models类的,与user类的所有
        # print(args)    # (类名,父类,类的名称空间)
        # print(kwargs)   空
        # print(class_name)
        # print(class_base)
        # print(class_dict)
        # 类的所有东西
        '''
        Models
        (<class 'dict'>,)
        {'__module__': '__main__', '__qualname__': 'Models', '__init__': <function Models.__init__ at 0x0000022B6753EBF8>, '__getattr__': <function Models.__getattr__ at 0x0000022B6753EC80>, '__setattr__': <function Models.__setattr__ at 0x0000022B6753ED08>, '__classcell__': <cell at 0x0000022B674E91C8: empty>}
        
        User
        (<class '__main__.Models'>,)
        {'__module__': '__main__', '__qualname__': 'User', 'table_name': 'user_info', 'user_id': <__main__.IntegerField object at 0x0000022B674EEDA0>, 'user_name': <__main__.StringFiled object at 0x0000022B674EEF28>, 'pwd': <__main__.StringFiled object at 0x0000022B674EE048>}

        '''
        # 1.剔除models类
        if class_name == 'Models':
            # 是models类就原路返回,什么也不做
            return type.__new__(cls, class_name, class_base, class_dict)
        # 2.设定一张表必须有一个表名
            # 从类的名称空间中获取table_name的值,如果没有就以类名作为表名
        table_name = class_dict.get('table_name',class_name)
        # print(table_name)   # user_info
        # 3.主键名默认为空
        primary_key = None
        # 4.定义一个空字典,用于存放所有的 字段 对象
        mappings = {}
            # 遍历名称空间
        for k,v in class_dict.items():
            # print(k,v)
            '''
            __module__ __main__
            __qualname__ User
            想要的是以下字段
            table_name user_info
            user_id <__main__.IntegerField object at 0x00000287AB9BED68>
            user_name <__main__.StringFiled object at 0x00000287AB9BEEF0>
            pwd <__main__.StringFiled object at 0x00000287AB9BEF28>'''
            # 过滤字段,获取想要的字段
            if isinstance(v,Field):
                # 给字典进行赋值保存字段
                mappings[k] = v
                # print(v)
                ''' v 就是每个字段对象
                <__main__.IntegerField object at 0x000001E6B993EDD8>
                <__main__.StringFiled object at 0x000001E6B993EE10>
                <__main__.StringFiled object at 0x000001E6B993EF98>'''
        # print(mappings)
                ''' 字典中字段对应的值都是对象.
                {'user_id': <__main__.IntegerField object at 0x000002056198E048>, 'user_name': <__main__.StringFiled object at 0x000002056198EEF0>, 'pwd': <__main__.StringFiled object at 0x000002056198EF28>}'''
                # 5.判断主键是否是唯一
                #     先判断字段对象中是否存在主键,对象.属性的方法获取primary_key
                if v.primary_key:
                    # 如果存在判断标记主键是否有值,有值说明循环过一次了
                    if primary_key:
                        raise TypeError('只能有一个主键')
                    # 主键标识不存在,则给primary_key赋值,防止重复
                    primary_key = v.name    # 赋值为当前具有主键的字段名

        # 6.节省资源,将mappings字典中和名称空间的重复元素删除
        for k in mappings.keys():
            class_dict.pop(k)


        # 判断是否为主键
        if not primary_key:
            raise TypeError('必须有一个主键')

        # 给类的名称空间中添加表名
        class_dict['table_name'] = table_name
        # 给类的名称空间中添加主键名
        class_dict['primary_key'] = primary_key
        # 给类的名称空间添加mappings字典,字典中是所有字段以及对象
        class_dict['mappings'] = mappings

        return type.__new__(cls,class_name,class_base,class_dict)






# 创建模型表类
#     1.创建表类的重复 --- 继承
#     2.每一张表的字段数量,名字都不一样,无法继承同一父类
#         --- 父类继承dict,dict是对象,继承dict,触发内部__init__(可接受任意数量已经类型属性)
#         dict = {key:value}    与  dict(key=value)  结果一致
#     3.字典的取存值方式有限,改为对象.属性的方式存取值
#         存, 对象.属性没有时触发    __getattr__
#         取, 对象.属性调用时触发    __setattr__


class Models(dict,metaclass=MyMetaclass):
    # 1.使用字典接受所有的关键字参数
    def __init__(self,**kwargs):
        super().__init__(**kwargs)  # kwargs接受所有的关键字参数
    # 2.取值时使用getattr,self是调用者本身item是没有的属性(key)
    def __getattr__(self, item):
        return self.get(item)
    # 3.存值时使用setattr,将调用者本身的字典中的key赋值保存为value
    def __setattr__(self, key, value):
        self[key] = value



class User(Models):
    # 自定义表名
    table_name = 'user_info'
    # 定义字段
    user_id = IntegerField(name='user_id',primary_key=True)
    user_name = StringFiled(name='user_name')
    pwd = StringFiled(name='pwd')


if __name__ == '__main__':
    s=User()
    print(s.mappings)
    '''
    {'user_id': <__main__.IntegerField object at 0x0000013DFA6BEDA0>, 'user_name': <__main__.StringFiled object at 0x0000013DFA6BEDD8>, 'pwd': <__main__.StringFiled object at 0x0000013DFA6BEF60>}'''
    print(s.table_name) # user_info
    print(s.primary_key)    # user_id

数据的操作

pymysql的使用

'''pymysql的连接使用'''
import pymysql

class MySQLClient:
    # 1.创建连接
    def __init__(self):
        # 连接客户端
        self.client = pymysql.connect(
            host = '127.0.0.1',
            port = 3306,
            user = 'root',
            password = '',
            database = 'youku',
            charset = 'utf8',
            autocommit = True   # 自动增长
        )
        # 创建游标对象并设置自动提交
        # 类似于原来的
        #     conn = pymysql.connect(user='root', password='', database='oldboy')
        #     cursor = conn.cursor()
        self.cursor = self.client.cursor(
            pymysql.cursors.DictCursor      # 输出字典格式
        )

    # 2.定义提交查询sql命令
    def my_select(self,sql,value=None):
        # 提交查询的sql指令
        self.cursor.execute(sql,value)
        # 获取查询以后得结果
        res = self.cursor.fetchall()
        return res

    # 3.封装'提交sql命令,插入或者更新'的方法
    def my_execute(self,sql,values):
        try:
            self.cursor.execute(sql,values)
        except Exception as e:
            print(e)

    # 4.关闭数据库
    def close(self):
        self.cursor.close()
        self.client.close()

select查询语句

#     查看    定义为类方法,可以类这调用
    @classmethod
    def select(cls,**kwargs):  #**kwargs接受所有的关键字参数
        # print(kwargs)   # 打散关键字参数 {'name': 'tank'}
        # 将MySQLClient实例化得到对象
        mysql = MySQLClient()
        # 定义sql语句: select * from 表名 where 条件
        # 判断传入的关键字参数是否有值
        if not kwargs:
            # 1.不存在,直接查询所有
            sql = 'select * from %s' % cls.table_name
                # 调用MySQLClient接口函数查询数据
            res = mysql.my_select(sql)

        else:
            # 2.存在,按条件查询 sql: select * from User where id=1;
            key = list(kwargs.keys())[0]    # 返回的是一个对象,需要转list
            # print(kwargs.keys())  # dict_keys(['name'])
            # print(list((kwargs.keys())))  # ['name']
            value = kwargs.get(key)
            # 防止sql注入问题,用户输入的都用?替代
            sql = 'select * from %s where %s=?' % (cls.table_name,key)
            sql = sql.replace('?','%s')     # 将?替换成%s 在execute中传参防止注入
            # 将sql语句与条件values传入查询函数方法
            res = mysql.my_select(sql,value)
            # print(res)
            # 将res列表中的字典调用cls自己本身,将其转变为对象
        return [cls(**d) for d in res]
    
运行----------------------------------

if __name__ == '__main__':
    # 查
    # 将select设置为类的绑定方法,可直接类调用
    res = User.select(user_name = 'tank')
    print(res)  # [{'user_id': 1, 'user_name': 'tank', ' pwd': '123'}]
    print(res[0])   # {'user_id': 1, 'user_name': 'tank', ' pwd': '123'}
    print(res[0].user_name)     # tank
    
    
    res = User.select()
        print(res)      # [{'user_id': 1, 'user_name': 'tank', ' pwd': '123'}, {'user_id': 2, 'user_name': 'nick', ' pwd': '321'}]

insert 插入数据

 # 插入数据
    def orm_insert(self):
        mysql = MySQLClient()
        # sql = 'insert into 表名(字段名) values (值1,值2);

        # 存储字段名
        keys = []
        # 存储字段对应的值,用于传参
        values = []
        # 存储问号,有几个?就存储几个
        args = []
        # print(self.mappings)
        '''{'user_id': <__main__.IntegerField object at 0x000001789E2600F0>, 'user_name': <__main__.StringFiled object at 0x000001789E2602E8>, 'pwd': <__main__.StringFiled object at 0x000001789E260320>}'''
        for k,v in self.mappings.items():
            # print(1,k,v)
            '''1 user_id    <__main__.IntegerField object at 0x000001B9D25EE128>
             1 user_name    <__main__.StringFiled object at 0x000001B9D25EE320>
             1 pwd <__main__.StringFiled object at 0x000001ECAE8DF320>'''
            # 过滤掉主键,主键是自增的所以不会传值
            # print(v.primary_key)    # True  False,v是user_id或user_name的对象,是否有.属性的primarykey方法
            if not v.primary_key:
                # print(v.name)   # user_name     pwd
                # 去除掉主键之后,获取其他的
                keys.append(v.name)     # 字段的属性
                # print(v.column_type)    # column_type
                # print(v.name)    # user_name    print(k)    # user_name   k=v.name
                # print(keys)     # ['user_name', 'pwd']   将所有的key保存至列表中
                # 存表中除了主键以外的字段值,若值没有,则使用默认值
                values.append(
                    getattr(self,v.name,v.default)  # 通过反射获得self中的name对应的值
                )   # self是他传进来的关键字参数,被打散为字典,也可按key取值

                # print(v)    # <__main__.StringFiled object at 0x000002B56E39E358>
                # print(v.name,v.default)      # user_name None
                # print(self)  #  {'user_name': '小明', 'pwd': 123}
                # print(self.get(v.name))     # 小明
                # print(getattr(self,v.name))     # 小明
                # print(values)               # ['小明']
                # 存放?号的,有几个字段,添加几个?
                args.append('?')
                # print(args)     # ['?', '?']

        # 编写sql语句指令
        sql = 'insert into %s(%s) values (%s)' % (
            self.table_name,
            ','.join(keys),     # ['?', '?']变为 user_name,pwd
            ','.join(args)
        )
        # 将?替换
        sql = sql.replace('?','%s')
        # 传值到上传函数
        mysql.my_execute(sql,values)
        # print(keys)

----------------------------------------------------------
if __name__ == '__main__':
    # 增
    obj = User(user_name='nihao',pwd=123)
    obj.orm_insert()
    res = User.select()
    print(res)
    '''
[{'user_id': 1, 'user_name': 'tank', 'pwd': '123'}, {'user_id': 2, 'user_name': 'nick', 'pwd': '321'}, {'user_id': 3, 'user_name': 'nihao', 'pwd': '123'}, {'user_id': 4, 'user_name': 'nihao', 'pwd': '123'}]'''

修改更新数据

 # 更新方法数据(修改数据)固定用主键当做查询方法
    def orm_update(self):
        print(0,self)       # 0 {'user_id': 3, 'user_name': '哈哈哈', 'pwd': '123'}
        '''sql = update 表名 set k1=v1, k2=v2 where id=主键值'''
        myslq = MySQLClient()
        # 字段名
        keys = []
        # 字段值
        values = []
        # 主键内容 id=主键名
        primary_key = None  # 主键名 = 主键序列号   id = 12

        for k,v in self.mappings.items():
            # print(0,k,v)
            # 0 user_id <__main__.IntegerField object at 0x000001A110BCF1D0>
            # 0 user_name <__main__.StringFiled object at 0x000001A110BCF400>
            # 0 pwd <__main__.StringFiled object at 0x000001A110BCF438>

            # 判断有无主键的存在,得到有主键的字段,便于where条件查询
            if v.primary_key:
                primary_key = v.name + '= %s' % getattr(self,v.name)
                # print(1,primary_key)      # 1 user_id= 3
            else:
                # 剩下的没有主键的字段都是想要修改的字段与新值
                keys.append(v.name + '=?')
                values.append(
                    getattr(self,v.name)  # 将用户传入的参数利用getattr方法进行获得值
                )
                # print(2,self)
                # 2 {'user_id': 3, 'user_name': '哈哈哈', 'pwd': '123'}
                # 2 {'user_id': 3, 'user_name': '哈哈哈', 'pwd': '123'}
                # print(3,getattr(self,v.name))
                #     3 哈哈哈
                #     3 123
        # 语句# sql: update table set k1=?, k2=? where id=pk; #
        sql = 'update %s set %s where %s' % (
            self.table_name,
            ','.join(keys),
            primary_key
        )
        # print(4,sql)
        # 4 update user_info set user_name=?,pwd=? where user_id= 3

        # 替换其中的?号
        sql = sql.replace('?','%s')
        myslq.my_execute(sql,values)
        
        
---------------------------------------------------------
if __name__ == '__main__':
    # 修改更新
    user_obj = User.select(user_name='hhh')[0]
    # print(user_obj) # {'user_id': 3, 'user_name': 'nihao', 'pwd': '123'}
    user_obj.user_name = '哈哈哈'
    # 对象.属性获得字段,将字段赋值为'哈哈哈',调用update函数将自身传入进去,然后根据sql语句更新
    user_obj.orm_update()
原文地址:https://www.cnblogs.com/fwzzz/p/11818908.html