Erlo

ORM对象关系映射

2020-11-17 22:00:20 发布   178 浏览  
页面报错/反馈
收藏 点赞

ORM

  ORM对象关系映射:将对象映射成数据表中的一条条记录

 

  类       映射为   ---> 数据表名

  对象            ---> 数据记录(一条条数据    比如: "张三 18岁 男")

  对象.属性         ---> 数据字段(一条条数据中的具体数据    比如: "男" 或者 "18岁" 或者 "张三")

 

# 演示映射关系(伪代码)

"""
比如User表中有名字,年龄,性别:
    User表:
        名字、 年龄、 性别
        apple、80、female


class User:    # User为表名
    pass


user_obj = User()  # 获取记录
user_obj.name属性  # 获取user表中的name数据信息
user_obj.age属性 = 80  # 给user表添加数据
"""

 

 

# 字段类型的定义,每个字段都应该有 字段名、字段类型、是否为主键、是否有默认值

# 字段类型父类
class Field:
    def __init__(self, name,
                 column_type,
                 primary_key=False, default=None):
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default


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

 

 

# Models类

  # 问题1:每一张表类,都必须定义一个__init__方法,但是无法确定每张表的字段个数以及字段名字

  # 解决1:通过继承dict字典类,让用户输入所需要添加的字段以及字段名字,解决该问题

  # 问题2:继承的字典无法通过 "对象.属性" 的取值方式来取值,也无法通过 "对象.属性=属性值" 的方式来赋值

  # 解决2:__getattr__方法解决了取值方式,__setattr__方法解决了赋值方式

# 1.让Models类继承dict字典类,所有子类继承Models就等于继承了dict类
class Models(dict):

    # 2.在 "对象.属性" 获取属性时,若 "没有该属性" 时触发 __getattr__
    def __getattr__(self, item):
        # print(item)
        return self.get(item)

    # 2.当 "对象.属性 = 属性值" ,"添加或修改属性" 时触发 __setattr__
    def __setattr__(self, key, value):
        self[key] = value


# 假如User表中有 用户名和密码 两个字段
class User(Models):
    pass


if __name__ == '__main__':
    # print(dict(username='apple', password='123'))    # {'username': 'apple', 'password': '123'}

    # User类继承了dict类,相当于 User(username='apple', password='123') == dict(username='apple', password='123')
    user_obj = User(username='apple', password='123')
    print(user_obj)
    # print(res.get("username"))    # 字典的取值方式一
    # print(res["username"])    # 字典的取值方式二

    print(user_obj.username)  # 字典的取值方式三,也是我们需要实现的取值方式  --->   " user_obj.name属性  # 获取user表中的name数据信息 "

    print('添加属性前: ---> ', user_obj)
    # User类实例化出来的 user_obj 普通对象 添加属性的方式
    user_obj.user_type = "admin"  # 实现通过 对象.属性=属性值 添加数据  --->   " user_obj.age属性 = 80  # 给user表添加数据 "
    print(user_obj.user_type)
    print('添加属性后: ---> ', user_obj)  # 验证是否将 user_type="admin" 添加到名称空间中

  执行结果:

{'username': 'apple', 'password': '123'}
apple
添加属性前: --->  {'username': 'apple', 'password': '123'}
admin
添加属性后: --->  {'username': 'apple', 'password': '123', 'user_type': 'admin'}

 

 

# 元类控制表类的创建

  字段类型的定义 + Model类 + 元类

  # 创建表的三个注意点:

    1.保证一张表必须要有表名字

    2.保证一张表中只能有一个主键

    3.将所有 "字段名"与"字段对象" 添加到一个独立的字典(mappings)中,以key(字段名):value(字段对象) 添加到类的名称空间中,方便后期使用

# 字段类型父类
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


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


# int字段类型
class IntegerField(Field):
    def __init__(self, name, column_type="varchar(64)", primary_key=False, default=0):
        super().__init__(name, column_type, primary_key, default)


# 自定义OrmMetaclass元类:控制类的创建过程
class OrmMetaclass(type):

    # __call__:先调用__new__,只要定义类就会触发__new__
    def __new__(cls, class_name, class_bases, class_dict):  # class_name:类名    class_bases:基类   class_dict:类的名称空间
        # print(f"类名:{class_name}")
        # print(f"基类:{class_bases}")
        # print(f"类的名称空间:{class_dict}")

        # 过滤掉Models类,我们限制的是用户创建表的过程(可以说是用户创建表的规范),所以在此处过滤掉Models类,因为我们不限制Models类
        if class_name == 'Models':
            return type.__new__(cls, class_name, class_bases, class_dict)

        # 第一个注意点:保证表必须要有表名。  获取table表名,若自定义则获取、没有则默认使用类名
        # dict.get(key, class_name)  key若有返回对应的值,若没有则返回默认值class_name就是默认值
        table_name = class_dict.get("table_name", class_name)
        # print(table_name)    # User

        # 自定义一个主键值标识,后面做逻辑判断使用
        # 主键值:主键名为 字段名
        primary_key = None  # 比如主键是id,那么id字段就是主键值的名字(primary_key = id)
        # 第三个注意点:定义一个字典用来存放 "字段名"与"字段对象"
        mappings = {}

        print(class_dict)    # 打印类中的名称空间
        """
        执行结果:
            {'__module__': '__main__', 
            '__qualname__': 'User', 
            'id': <__main__.IntegerField object at 0x000000000287F1C8>, 
            'username': <__main__.StringField object at 0x000000000287F088>, 
            'password': <__main__.StringField object at 0x000000000287F388>}
        
        上面class_dict的名称空间中,我们需要的仅仅是后面的id、username、password键值对属性,前两个不需要,我们需要将其剔除,后面将剔除后的结果重新放进mappings字典中
        """
        # 使用for循环将类中的名称空间字典 迭代依次取出
        for k, v in class_dict.items():  # k为字段名,v为字段对象

            # 将类中的名称空间无需的属性进行剔除筛选
            # isinstance():判断一个对象是否是另一个类的实例
            if isinstance(v, Field):  # id、username、password的value都是Field的实例
                # print(k, v)
                # print(v.__dict__)    # {'name': 'password', 'column_type': 'varchar(64)', 'primary_key': False, 'default': None}

                # 第三个注意点:将所有的 "字段名" 与 "字段对象" 添加到一个定义好的独立的字典(mappings)中
                mappings[k] = v
                """
                # 此处有一个坑:
                # class_attr.pop(k)    # 这里当字典被迭代时,不能修改其属性
                """

                # 第二个注意点:保证一张表只能有一个唯一的主键
                # 判断字段对象如果有主键 primary_key,则为primary_key变量赋值
                if v.primary_key:  # 如果建的表中有主键,执行下一步,若没有不执行下一步
                    # 若第二次执行到此处,primary_key有值,证明有主键,抛出异常
                    if primary_key:
                        raise TypeError("一张表只能有一个主键!")
                    primary_key = v.name  # 将自定义的主键值标识修改为 字段对象中的名字(和字段名一样)

        # 给类的名称空间 添加table_name、primary_key、mappings属性
        class_dict["table_name"] = table_name
        class_dict["primary_key"] = primary_key
        class_dict["mappings"] = mappings

        print(class_dict)
        """
        执行结果:
            {'__module__': '__main__', 
            '__qualname__': 'User', 
            'id': <__main__.IntegerField object at 0x000000000237F1C8>, 
            'username': <__main__.StringField object at 0x000000000237F088>, 
            'password': <__main__.StringField object at 0x000000000237F388>, 
            'table_name': 'User', 
            'primary_key': 'id', 
            'mappings': {'id': <__main__.IntegerField object at 0x000000000237F1C8>, 
                        'username': <__main__.StringField object at 0x000000000237F088>, 
                        'password': <__main__.StringField object at 0x000000000237F388>}}

        上面class_dict的名称空间中,有重复的"字段名"与"字段对象",在此将class_dict中重复的"字段名"与"字段对象"过滤掉
        """

        # 3.过滤掉类名称空间中重复的字段属性
        for key in mappings.keys():  # 根据mappings中的字段属性来过滤掉class_dict中的字段属性,因为mappings中的字段属性在class_dict中的字段属性都存在
            class_dict.pop(key)

        print(class_dict)
        """
        执行结果:
            {'__module__': '__main__', 
            '__qualname__': 'User', 
            'table_name': 'User', 
            'primary_key': 'id', 
            'mappings': {'id': <__main__.IntegerField object at 0x000000000283F1C8>, 
                        'username': <__main__.StringField object at 0x000000000283F088>, 
                        'password': <__main__.StringField object at 0x000000000283F388>}}

        过滤完成!
        """

        # 若建立的表中所有字段都没有主键(规定每一张表中都需要有一个主键),抛出异常
        if not primary_key:
            raise TypeError("表中必须要有一个主键!")

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


# 让Models类继承dict字典类,所有子类继承Models就等于继承了dict类
class Models(dict, metaclass=OrmMetaclass):

    # 在 "对象.属性" 获取属性时,若 "没有该属性" 时触发 __getattr__
    def __getattr__(self, item):
        # print(item)
        return self.get(item)

    # 当 "对象.属性 = 属性值" ,"添加或修改属性" 时触发 __setattr__
    def __setattr__(self, key, value):
        self[key] = value


# 假如User表中有 id、用户名和密码 三个字段
# 问题3:一个标准中只能有一个唯一的主键,在当前表类中,无法控制用户定义类的行为
class User(Models):
    # 字段类中的name属性必须与User表中类属性同名
    id = IntegerField(name="id", primary_key=True)
    username = StringField(name="username")
    password = StringField(name="password")

  测试代码:

    (错误演示)第一种:创建字段时不添加主键primary_key

    (错误演示)第二种:创建字段时添加多个主键primary_key

     (正确演示):创建字段时只添加一个主键primary_key

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认