zoukankan      html  css  js  c++  java
  • Python实现简易的ORM模型

    本文记录下自己使用Python实现一个简易的ORM模型

    使用到的知识

    1、元类

    2、描述器

    元类

    对于元类,我的理解其实也便较浅,大概是这个意思

    所有的类都是使用元类来进行创建的,而所有的类的父类中必然是object(针对Python3),Python中的元类只有一个(type),当然这里不包含自定义元类

    下面我们来看下类的创建

    class Test:   # 定义一个类
        pass
    
    Test1 = type("Test2",(object,),{"name":"test"})  # 定义一个类
    
    
    print(type(Test))
    print(type(Test1))
    -----------------------

    <class 'type'>
    <class 'type'>

    从上面可以看出创建类,其实是有两种方式,一种是通过class关键字来定义,一种是通过type来进行创建,当然常用的是使用class来进行创建了,在看最后的结果,可以看出类的类型为type。说明我们这个类就是由type来创建的

    明白了这个之后我们再来梳理下怎么使用自定义元类来创建类,明白一点,自定义元类需要继承type

    class MetaClass(type):  # 定义一个元类
        pass
    
    
    class Test(metaclass=MetaClass):  # 使用自定义元类来创建类
        pass
    
    
    
    print(type(Test))
    
    
    --------------------------
    
    <class '__main__.MetaClass'>

    很明显可以看出Test类就是用MetaClass类创建出来的

    描述器

    从描述器的定义来说,只要一个类中实现了__get____set____delete__中的一个或几个,这个类的实例就可以叫描述器

    下面我们来定义一个简易的描述器

    class Describer:
    
        def __set__(self, instance, value):
            print("设置属性的时候会被调用")
            self.value = value
    
        def __get__(self, instance, owner):
            print("获取属性的时候会被调用")
            return self.value
    
        def __delete__(self, instance):
            print("删除属性的时候会被调用")
            self.value = None
    
    
    class Test:
        name = Describer()
    
    
    
    t = Test()
    t.name = "xxxxx"
    print(t.name)
    
    ----------------------
    
    设置属性的时候会被调用
    获取属性的时候会被调用
    xxxxx

    从上面的代码中有没有什么想法?既然__set__方法会在我们设置属性的时候会被调用,那么我们是不是可以在设置属性前对这个属性做一些操作呢?

    ORM模型

    ORM模型到底是个啥?ORM对于后端研发来说肯定是不陌生的,包括很多后端框架现在都自带这个模型了

    ORM(Object Relational Mapping)对象关系映射

    既然是对象关系映射,那对象是啥?我的理解为:Python中的类与数据库之间的映射,对数据的操作就不用编写SQL语言了,因为都封装好了,比如你想插入一条数据,你就直接创建一个对象即可,

    Python ------->>>>      数据库

    类名     ------->>>>      数据库中的表名

    对象     ------->>>>      数据库中的一行数据

    属性     ------->>>>      数据库中的字段

    大致就是上面的映射关系

    ORM实现步骤

    1、利用描述器实现对数据库字段的类型、长度限制

    2、实现Mode类,也就是Python中的类

    3、利用元类实现映射关系

    好,我们先利用描述器来实现对数据字段的类型,长度限制

    class BaseFiled:
        pass
    
    
    class CharFiled(BaseFiled):
        """定义一个字符串的类型限制"""
    
        def __init__(self, length=10):
            self.length = length
    
        def __set__(self, instance, value):
            if isinstance(value, str):
                if len(value) <= self.length:
                    self.value = value
                else:
                    raise ValueError("length can not exceed {}".format(self.length))
            else:
                raise TypeError("need a str")
    
        def __get__(self, instance, owner):
            return self.value
    
        def __delete__(self, instance):
            self.value = None
    
    
    class IntFiled(BaseFiled):
        """定义一个数值的类型限制"""
    
        def __set__(self, instance, value):
            if isinstance(value, int):
    
                self.value = value
            else:
                raise TypeError("need a int")
    
        def __get__(self, instance, owner):
            return self.value
    
        def __delete__(self, instance):
            self.value = None
    
    
    class BoolFiled(BaseFiled):
        """定义一个布尔的类型限制"""
        def __set__(self, instance, value):
            if isinstance(value, bool):
    
                self.value = value
            else:
                raise TypeError("need a bool")
    
        def __get__(self, instance, owner):
            return self.value
    
        def __delete__(self, instance):
            self.value = None

    上面实现了三种,分别是字符串、数值、布尔值的,下面在来实现元类以及模型类

    class MyMateClass(type):
        """自定义一个元类"""
        def __new__(cls, name: str, bases: tuple, dic: dict, *args, **kwargs):
            """
    
            :param name: name为模型类的类名也就是数据库中的表名
            :param bases:  bases为一个元祖类型,里面装的是name这个类的父类
            :param dic: dic为一个dict类型,装的是name这个类中的属性
            :param args:
            :param kwargs:
            :return:
            """
            if name == "BaseMode":   # 判断类名是否为BaseMode,如果是则直接使用元类创建类,不做其他任何操作
                return super().__new__(cls, name, bases, dic)
            else:
                table_name = name.lower()  # 将表名变成小写
                filed_dic = {}   # 定义一个空的列表,用来装dic中属于BaseFiled类型的属性,因为dic中会有其他创建类时自动生成的属性,这些属性我们没必要去建立映射关系,所以需要将其剔除掉
                for k, v in dic.items():
                    if isinstance(v, BaseFiled):
                        filed_dic[k] = v
                dic["t_name"] = table_name    # 将表名添加到dic中,实现类名与表名的映射关系
                dic["filed_dict"] = filed_dic  # 将属于BaseFiled类型的属性给添加到dic中,实现属性与字段的映射关系
                return super().__new__(cls, name, bases, dic)
    
    
    class BaseMode(metaclass=MyMateClass):
    
        def __init__(self, **kwargs):
            """
            由于每一个模型类(也就是数据库表)的属性个数不一致,所以我们需要定义一个父类来进行定义初始化的属性
            :param kwargs:
            """
            for k, v in kwargs.items():   # 遍历传进来的所有属性
                setattr(self, k, v)   # 拿到这些属性后对self(也就是类本身)进行设置属性
    
        def save(self):
            """生成SQL语句"""
            # 获取表名
            table_name = self.t_name
            # 获取所有的属性
            fileds = self.filed_dict
            dic = {}  # 定义一个空字典,用来装属性名和属性值
            for k, v in fileds.items():
                value = getattr(self, k)
                dic[k] = value
            sql = "insert into {} values{}".format(table_name, tuple(dic.values()))
            return sql
    
    
    class User(BaseMode):
        name = CharFiled()
        age = IntFiled()
        love = CharFiled(length=50)
        live = BoolFiled()
    
    
    if __name__ == '__main__':
        c = User(name="lc", age=12, love="hjh", live=True)
        c.save()
    
    
    
    
    --------------------------
    insert into user values('lc', 12, 'hjh', True)

    以上就实现了一个简单的ORM模型了,这个虽然在测试开发过程中用的很少(一般都是直接用框架中封装好的),学习这个也是为了更好的理解原理,后面好学习flask以及Django。

    下面贴一下完整的代码吧

    # -*- coding: utf-8 -*-
    # @Time    : 2021-05-11 10:14
    # @Author  : cainiao
    # @File    : Meat.py
    # @Software: PyCharm
    # @Content : 实现ORM模型
    
    class BaseFiled:
        pass
    
    
    class CharFiled(BaseFiled):
        """定义一个字符串的类型限制"""
    
        def __init__(self, length=10):
            self.length = length
    
        def __set__(self, instance, value):
            if isinstance(value, str):
                if len(value) <= self.length:
                    self.value = value
                else:
                    raise ValueError("length can not exceed {}".format(self.length))
            else:
                raise TypeError("need a str")
    
        def __get__(self, instance, owner):
            return self.value
    
        def __delete__(self, instance):
            self.value = None
    
    
    class IntFiled(BaseFiled):
        """定义一个数值的类型限制"""
    
        def __set__(self, instance, value):
            if isinstance(value, int):
    
                self.value = value
            else:
                raise TypeError("need a int")
    
        def __get__(self, instance, owner):
            return self.value
    
        def __delete__(self, instance):
            self.value = None
    
    
    class BoolFiled(BaseFiled):
        """定义一个数值的类型限制"""
    
        def __set__(self, instance, value):
            if isinstance(value, bool):
    
                self.value = value
            else:
                raise TypeError("need a bool")
    
        def __get__(self, instance, owner):
            return self.value
    
        def __delete__(self, instance):
            self.value = None
    
    
    class MyMateClass(type):
        """自定义一个元类"""
        def __new__(cls, name: str, bases: tuple, dic: dict, *args, **kwargs):
            """
    
            :param name: name为模型类的类名也就是数据库中的表名
            :param bases:  bases为一个元祖类型,里面装的是name这个类的父类
            :param dic: dic为一个dict类型,装的是name这个类中的属性
            :param args:
            :param kwargs:
            :return:
            """
            if name == "BaseMode":   # 判断类名是否为BaseMode,如果是则直接使用元类创建类,不做其他任何操作
                return super().__new__(cls, name, bases, dic)
            else:
                table_name = name.lower()  # 将表名变成小写
                filed_dic = {}   # 定义一个空的列表,用来装dic中属于BaseFiled类型的属性,因为dic中会有其他创建类时自动生成的属性,这些属性我们没必要去建立映射关系,所以需要将其剔除掉
                for k, v in dic.items():
                    if isinstance(v, BaseFiled):
                        filed_dic[k] = v
                dic["t_name"] = table_name    # 将表名添加到dic中,实现类名与表名的映射关系
                dic["filed_dict"] = filed_dic  # 将属于BaseFiled类型的属性给添加到dic中,实现属性与字段的映射关系
                return super().__new__(cls, name, bases, dic)
    
    
    class BaseMode(metaclass=MyMateClass):
    
        def __init__(self, **kwargs):
            """
            由于每一个模型类(也就是数据库表)的属性个数不一致,所以我们需要定义一个父类来进行定义初始化的属性
            :param kwargs:
            """
            for k, v in kwargs.items():   # 遍历传进来的所有属性
                setattr(self, k, v)   # 拿到这些属性后对self(也就是类本身)进行设置属性
    
        def save(self):
            """生成SQL语句"""
            # 获取表名
            table_name = self.t_name
            # 获取所有的属性
            fileds = self.filed_dict
            dic = {}  # 定义一个空字典,用来装属性名和属性值
            for k, v in fileds.items():
                value = getattr(self, k)
                dic[k] = value
            sql = "insert into {} values{}".format(table_name, tuple(dic.values()))
            return sql
    
    
    class User(BaseMode):
        name = CharFiled()
        age = IntFiled()
        love = CharFiled(length=50)
        live = BoolFiled()
    
    
    if __name__ == '__main__':
        c = User(name="lc", age=12, love="hjh", live=True)
        print(c.save())
        # c.name="lc"
        # print(c.name)
    View Code

    继续学习吧,奥利给!

  • 相关阅读:
    docker 安装镜像
    Vagrant+Oracle VM VirtualBox创建linux虚拟机(centos7)
    idea配置git,github , gitee
    idea连接数据库
    idea基本设置
    git基础命令
    mybatis中的where
    重学 Java 设计模式:实战桥接模式(多支付渠道「微信、支付宝」与多支付模式「刷脸、指纹」场景)
    HTTPS加密原理
    优惠券数据库设计
  • 原文地址:https://www.cnblogs.com/Pycainiao/p/14756759.html
Copyright © 2011-2022 走看看