ORM

orm简单配置:

'''
ORM: 对象关系映射
表 ---> 类
一条条的记录 ---> 对象
一条条记录下的字段 ---> 对象.属性

字段类型:
    varchar:
        字段名
        字段类型
        是否为主键
        默认值

从底层的字段类型开始编写
然后创建类对象
'''
from mysql_control import Mysql


# 创建字段类
# 字段类型都需要定义一些共有的(字段名、字段类型、是否为主键、默认值)属性,所以抽象一下,先定义一个父类
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和varchar类型的字段类型,并继承Field这个父类
class IntegerField(Field):
    # 初始化(生成对象的时候)设置一些默认值
    def __init__(self, name, column_type='int', primary_key=False, default=0):
        super().__init__(name, column_type, primary_key, default)


class StringField(Field):
    def __init__(self, name, column_type='varchar(255)', primary_key=False, default=None):
        super().__init__(name, column_type, primary_key, default)


# 创建类(表)对象
# 创建类(表)对象时候需要对其进行一定的限制--->元类可以控制创建的类
# 定义自己的元类,名字随便(见名知意即可)
class OrmMetaClass(type):
    # 创建类的时候需要指定三个参数    分别为   类名、类的父类、类的名称空间
    # 创建一个类一定触发 __new__ 方法   所以对此方法进行修改
    def __new__(cls, class_name, class_base, class_attr):
        # 因为我们是在创建具体一个类的时候进行限定,所以对这些类的抽象父类不进行限定
        # 过滤出抽象父类
        if class_name == 'Models':   # 这里面的Models是自己创建的抽象父类名称,可以随意同步更改
            return type.__new__(cls, class_name, class_base, class_attr)  # 如果是抽象父类,则不进行修改直接返回

        # 不是抽象父类
        # 需要对表(orm 将类映射为表)的  表名、主键、字段 进行限制

        # 1.如果这个表没有定义表名,那么就把类名当作表名
        table_name = class_attr.get('table_name', class_name)  # 使用get获取字典的值,没有值则返回默认值--->类名

        # 2.限定表只有一个主键
        primary_key = None  # 先将主键设为空
        mappings = {}  # 用来存放所以的表字段(仅仅存放表字段),方便取值

        for key, value in class_attr.items():  # 遍历类名称空间中的所有items ---> 键值对的形式
            # 将字段类的对象添加到 mappings 中

            # 判断 value 是否为字段类的对象
            if isinstance(value, Field):
                mappings[key] = value  # 仅仅将表的字段名加进去,其他的类属性不要

                # 判断这个表对象的主键属性是否为True
                if value.primary_key:

                    # 判断主键是否已经设定(是否已经有一个主键了)---> 设定过后全局名称空间中的 primary_key 就会被修改,不在为None
                    if primary_key:  # 不为 None 则报下面的错
                        raise TypeError('only one primary_key should be set in one table!')  # 报错信息:一张表只能设置一个主键

                    # 没有设定则获取主键,同时全局变量 primary_key 也被修改了
                    primary_key = value.name
                else:
                    pass
            else:
                pass

        # 删除名称空间中与 mappings 重复的属性,节省资源(额外操作,可有可无)
        for key in mappings.keys():  # mappings是个字典,使用key()方法可以取出所有的 key
            class_attr.pop(key)

        # 判断是否有主键
        if not primary_key:
            raise TypeError('must have one primary_key!')

        # 对名称空间中的值进行修改(重新赋值)
        class_attr['table_name'] = table_name
        class_attr['primary_key'] = primary_key
        class_attr['mappings'] = mappings

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


# 元类创建好了,但是所有的表对象都需要写 init 方法,所以我们可以将 init 方法抽象为一个父类
# 由于我们不知道子类中有多少属性,所以可以使用 **kwargs 来接收任意关键字参数--->继承字典类型
class Models(dict, metaclass=OrmMetaClass):    # 继承字典,获取字典的方法;并指定元类,限制类的生成
    def __init__(self, **kwargs):  # **能够将字典中的键值对打散
        super().__init__(**kwargs)  # 掩盖了父类的初始化方法,就需要重新调用一下

    # 由于字典类没有 点属性法 查看值以及修改值,所以我们需要自己设定

    # 对象.属性没有时候会触发 __getattr__ 方法
    def __getattr__(self, item):
        return self.get(item, '没有这个key')  # 自定义返回值,且可以设定返回的默认值

    # 对象.属性 = 属性值  的时候触发 __setattr__ 方法
    def __setattr__(self, key, value):
        self[key] = value  # 功能函数内进行复制操作,达到点属性赋值一样的效果

    # 每个类对象(表)都应具备查询数据的功能,且应为--->非绑定方法
    @classmethod  # 非绑定方法的关键词
    def select(cls, **kwargs):  # 查看的时候一般都是关键字参数条件
        # 先获取数据库连接对象
        ms = Mysql()

        # 判断**kwargs,若没有则代表没有查询条件,默认为查询所有
        if not kwargs:
            # sql语句格式本应为 select * from 表名,我们先用占位符 %s 占位
            sql = 'select * from %s' % cls.table_name  # 类对象中有类名这个属性--->也就是表名
            # 接收查询的结果,且返回的结果为大列表字典类型
            res = ms.my_select(sql)  # 因为没有条件,所以不需要进行sql语句的拼接

        # 若有 kwargs,则代表查询时候有限制条件
        else:
            # sql语句格式本应为 select * from 表名 where 条件(kwargs的字段名=kwargs的值)
            # 分别获取sql语句所需要的所有关键信息,并生成sql语句

            # 1.获取条件的key,也就是字段名
            key = list(kwargs.keys())[0]  # 条件为字典,用keys()方法取值;并将结果变成有序的列表形式,依据索引取值

            # 2.获取条件的value,也就是字段的值
            value = kwargs.get(key)  # 字典的取值方法

            # 3.获取表名,也就是 cls.table_name

            # 生成sql语句
            sql = 'select * from %s where %s = ?' % (cls.table_name, key)  # 为了防止sql注入问题,关键的值不要手动拼接;
            # 所以先用?替换(不进行占位),然后再替换回来,再用cursor方法拼接
            sql = sql.replace('?', '%s')  # 替换的过程也是占位的过程

            # 接收 连接对象提交sql语句执行后返回的结果;结果为 大列表套字典 类型
            res = ms.my_select(sql, value)  # 防止sql注入问题,能自动识别sql语句的 %s

        # 判断返回的结果有无,并将结果以对象的形式返回
        if res:
            # 使用列表生成式,得到的是列表套对象
            return [cls(**result) for result in res]  # 将结果遍历,得到一个个字典;将字典变成对象,然后用一个大列表包裹
        else:
            pass

    # 插入数据
    def insert(self):
        # 先获取数据库连接对象
        ms = Mysql()

        # sql语句格式本应为 insert into 表名(字段名) values(各个字段对应的值); 我们先用占位符 %s 占位
        # sql = 'insert into %s(%s) values(%s)' % (...)
        # 表名---> cls.table_name
        # 字段名以及字段的值不知道

        # 1.定义存放字段名、字段值以及用于存放对应字段的?号的空列表
        fields = []  # 存放字段名
        values = []  # 存放字段的值
        args = []  # 存放对应字段的?号

        # 2.获取字段名,字段值;(过滤掉主键,因为插入数据时候不需要给主键传值)
        for k, v in self.mappings.items():  # mappings在定义元类的时候就已经限制把表中的字段名与之存放其中了
            # 过滤主键
            if not v.primary_key:  # 判断值这个对象的主键属性是否为 True,不是则继续
                fields.append(v.name)  # 将字段的名添加进去
                values.append(getattr(self, v.name, v.default))  # 将字段的值添加进去,没有值则添加默认值
                # v.name 是字段名         getattr(v.name) 是字段的值---> 映射
                args.append('?')  # 遍历循环,有多少个值就有多少个?号

        # 生成sql语句
        # fields与args是列表[x,x,x],但是我们的sql语句对应的形式是(x,x,x),所以进行,号拼接成目的格式
        sql = 'insert into %s(%s) values(%s)' % (self.table_name, ','.join(fields), ','.join(args))

        # 拼接好的sql语句中目前剩下?号这个符号,需要再次替换,以便cursor识别;同时防止sql注入问题
        sql = sql.replace('?', '%s')

        # 使用连接对象进行sql语句的提交(插入数据没有返回值)
        ms.my_execute(sql, values)  # 提交sql语句,并将values中的值一一对应到替换后的sql语句中

    # 更新数据
    def sql_update(self):
        # 先获取数据库连接对象
        ms = Mysql()

        # sql语句格式本应为 update 表名 set 字段名=字段值 where 字段名=字段值;    我们先用占位符 %s 占位(这里我们默认主键当作条件的字段名)
        # sql = 'update %s set %s where %s=%s' % (...)
        # 表名---> cls.table_name
        # 主键名---> cls.primary_key
        # 字段名、字段的值以及主键的值不知道

        # 1.定义存放字段名、字段值的空列表,并获取主键
        fields = []  # 存放字段名
        values = []  # 存放字段的值
        primary_key = None  # 先将主键的值设置为None,获取过后对其进行修改

        # 2.获取字段名,字段值,主键;(因为更新数据时候需要用主键定位)
        for k, v in self.mappings.items():   # mappings在定义元类的时候就已经限制把表中的字段名与之存放其中了

            # 获取主键的值
            if v.primary_key:  # 判断值里面主键这个属性是否为True,是就讲主键的值获取出来
                primary_key = getattr(self, v.name, v.default)  # 获取主键的值,没有值则添加默认值

            # 获取其他字段名与值
            else:  # 值里面的主键属性为False,表明不是主键,也就是其他的字段
                fields.append(v.name + '=?')  # 对字段名进行一个拼接,因为我们要设定的值是一个键值对的形式,而我们只知道字段名
                values.append(getattr(self, v.name, v.default))  # 将字段的值添加进去,没有值则添加默认值

        # 生成sql语句
        sql = 'update %s set %s where %s = %s' % (self.table_name, ','.join(fields), self.primary_key, primary_key)
        # 生成后的sql语句第二个%s对应的格式为   字段名= ?   因此再对?号进行替换,以便cursor识别,防止sql注入问题
        sql = sql.replace('?', '%s')

        # 使用连接对象进行sql语句的提交(更新数据没有返回值)
        ms.my_execute(sql, values)  # # 提交sql语句,并将values中的值一一对应到替换

    # 至此每个实例化产生的对象都具备了以上的方法(包括点属性取值,赋值以及查询、插入、修改数据的方法)


# 创建具体的对象,并继承Models,使其具备Models中的各个方法
class User(Models):
    # 在这里不定义表名就默认表名为类名
    # table_name = 'user_info'

    # 每个表(类)都应具备两种数据类型---> int类型和 varchar类型   两种类型都是只有name没有默认值,所以至少传入一个name参数才能实例化出一个属性对象
    id = IntegerField(name='id', primary_key=True)  # 一般每个表都有个id字段,且一般将id设置为主键字段
    # 注意:字段名=属性名   保持一致
    # 此外,还需要一个varchar类型,我们这里以name作为另一个类属性
    name = StringField(name='name')


# 测试
if __name__ == '__main__':
    d3 = User(id=1, name='tank')
    print(d3)

    # 取值
    # print(d3.get('name'))
    # print(d3.name)
    # print(d3['name'])

    # 赋值(修改)
    d3.name = 'zhao'
    print(d3.name)

mysql_control:

import pymysql


class Mysql:
    # 设置单例模式
    _instance = None

    def __new__(cls, *args, **kwargs):
        # 如果没有_instance就调用Mysql的父类Object中__nem__方法实例化对象,并赋值给_instance
        if not cls._instance:
            cls._instance = object.__new__(cls, *args, **kwargs)
            return cls._instance

    def __init__(self):
        # 建立连接
        self.conn = pymysql.connect(
            host='127.0.0.1',
            port=3306,
            user='user',
            passwd='123456',
            db='youku',
            charset='utf8',
            autocommit=True
        )
        # 生成游标
        self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)

    # 关闭游标、连接的方法
    def close_db(self):
        self.cursor.close()
        self.conn.close()

    # 查看
    def my_select(self, sql, args=None):
        # 提交查看的sql语句
        self.cursor.execute(sql, args)
        # 接收查看结果,并返回
        res = self.cursor.fetchall()
        return res  # 返回的是字典类型数据

    # 提交---> 包括插入数据,修改数据
    def my_execute(self, sql, args):
        # 不一定能够提交成功,所以使用异常捕获
        try:
            self.cursor.execute(sql, args)
        except Exception as e:
            print(e)
原文地址:https://www.cnblogs.com/sweet-i/p/11420837.html