ORM对象关系映射

ORM对象关系映射[Python基础]

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"}

ORM的作用(两点)

  第一个作用:控制表类的创建,也就是使用元类控制表创建的过程

  第二个作用:给表类封装  查、改(增、改、删)的方法,MySQL类的封装

一、元类控制表类的创建

  字段类型的定义 + 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) # 获取类名称空间的自定义表名table_name,若没有则默认以类名为表名

print(table_name) # userinfo,若未定义表名,那么表名为类名,此处即User

# 自定义一个主键值标识,后面做逻辑判断使用

# 主键值:主键名为 字段名

primary_key = None # 比如主键是id,那么id字段就是主键值的名字(primary_key = id)

# 第三个注意点:定义一个字典用来存放 "字段名"与"字段对象"

mappings = {}

print(class_dict) # 打印类中的名称空间

"""

执行结果:

{"__module__": "__main__",

"__qualname__": "User",

"table_name": "userinfo",

"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",

"table_name": "userinfo",

"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中重复的"字段名"与"字段对象"过滤掉

"""

# 过滤掉类名称空间中重复的字段属性

for key in mappings.keys(): # 根据mappings中的字段属性来过滤掉class_dict中的字段属性,因为mappings中的字段属性在class_dict中的字段属性都存在

class_dict.pop(key)

print(class_dict)

"""

执行结果:

{"__module__": "__main__",

"__qualname__": "User",

"table_name": "userinfo",

"primary_key": "id",

"mappings": {"id": <__main__.IntegerField object at 0x000000000283F1C8>,

"username": <__main__.StringField object at 0x000000000283F088>,

"password": <__main__.StringField object at 0x000000000283F388>}}

过滤完成!

"""

# 若建立的表中所有字段都没有主键(规定每一张表中都需要有一个主键),抛出异常

ifnot 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):

table_name = "userinfo"# 自定义一个表名,若此处不写,表名默认即为类名User

# 字段类中的name属性必须与User表中类属性同名

id = IntegerField(name="id", primary_key=True)

username = StringField(name="username")

password = StringField(name="password")

  测试代码:

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

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

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

二、MySQL类的封装

  给表类封装  查、改(增、改、删)的方法

mysql_setting.py:编写代码

import pymysql

# MySQL连接类

class MySQL:

__instance = None

@classmethod

def Singleton(cls):

if cls.__instance:

cls.__instance = cls()

return cls.__instance

# 实例化MySQL类时,获取数据库连接对象,获取游标对象

def__init__(self):

self.client = pymysql.connect(

host="127.0.0.1",

port=3306,

user="root",

passwd="123",

db="data",

charset="utf8",

autocommit=True

)

# 获取游标

self.cursor = self.client.cursor(pymysql.cursors.DictCursor)

# 自定义查询数据方法

def select(self, sql, args=None):

# 1.先提交查询sql语句

# sql = "select * from table"

# sql = "select * from table where id=%s"

self.cursor.execute(sql, args) # 假如查询语句中有where条件,则需要传入参数(避免sql注入,在execute中传参)

# 2.获取返回的查询结果

res = self.cursor.fetchall()

for data in res:

print(data)

# return res

# 自定义提交修改数据方法(添加、更新、删除)

def execute(self, sql, args=None):

# 提交sql语句

# sql = "insert into table(id, username) values(%s, %s)"

try:

self.cursor.execute(sql, args) # 此处一定会传入参数

except Exception as e:

print(e)

# 关闭游标、关闭连接

def close(self):

self.cursor.close()

self.client.close()

mysql_test.py:测试代码

from day38.db_control.mysql_setting import MySQL  # 导入写好的MySQL模块

if__name__ == "__main__":

# 查询数据

def select_data(sql, args=None):

mysql_obj = MySQL()

# sql = "select * from userinfo where id=%s"

mysql_obj.select(sql, args)

print(id(mysql_obj))

# 插入数据

def execute_data(sql, args=None):

mysql_obj = MySQL()

mysql_obj.execute(sql, args)

print(id(mysql_obj))

# 调用查询数据:查询插入数据前的数据

select_data("select * from userinfo")

"""

执行结果:

{"id": 1, "username": "tom", "password": "123456", "gender": "三年四班", "sex": "male", "phone_number": 13511111111}

{"id": 2, "username": "jack", "password": "456789", "gender": "三年一班", "sex": "male", "phone_number": 13622222222}

{"id": 3, "username": "jerry", "password": "147258", "gender": "三年二班", "sex": "male", "phone_number": 13733333333}

1990595350992

"""

# 插入数据

execute_data("insert into userinfo(username, password, gender, sex, phone_number) values(%s, %s, %s, %s, %s)",

("owen", "321456", "三年三班", "female", "15244444444"))

"""

执行结果:

1990595350992

"""

# 调用查询数据:查询插入数据后的数据

select_data("select * from userinfo")

"""

执行结果:

{"id": 1, "username": "tom", "password": "123456", "gender": "三年四班", "sex": "male", "phone_number": 13511111111}

{"id": 2, "username": "jack", "password": "456789", "gender": "三年一班", "sex": "male", "phone_number": 13622222222}

{"id": 3, "username": "jerry", "password": "147258", "gender": "三年二班", "sex": "male", "phone_number": 13733333333}

{"id": 4, "username": "owen", "password": "321456", "gender": "三年三班", "sex": "female", "phone_number": 15244444444}

1990595350992

"""

# 更新数据

execute_data("update userinfo set username=%s where username=%s", ("lucy", "owen"))

"""

执行结果:

1990595350992

"""

# 调用查询数据:查询更新数据后的数据

select_data("select * from userinfo")

"""

执行结果:

{"id": 1, "username": "tom", "password": "123456", "gender": "三年四班", "sex": "male", "phone_number": 13511111111}

{"id": 2, "username": "jack", "password": "456789", "gender": "三年一班", "sex": "male", "phone_number": 13622222222}

{"id": 3, "username": "jerry", "password": "147258", "gender": "三年二班", "sex": "male", "phone_number": 13733333333}

{"id": 4, "username": "lucy", "password": "321456", "gender": "三年三班", "sex": "female", "phone_number": 15244444444}

1990595350992

"""

# 删除数据

execute_data("delete from userinfo where username=%s", "lucy")

"""

执行结果:

1990595350992

"""

# 调用查询数据:查询删除数据后的数据

select_data("select * from userinfo")

"""

执行结果:

{"id": 1, "username": "tom", "password": "123456", "gender": "三年四班", "sex": "male", "phone_number": 13511111111}

{"id": 2, "username": "jack", "password": "456789", "gender": "三年一班", "sex": "male", "phone_number": 13622222222}

{"id": 3, "username": "jerry", "password": "147258", "gender": "三年二班", "sex": "male", "phone_number": 13733333333}

1990595350992

"""

以上是 ORM对象关系映射 的全部内容, 来源链接: utcz.com/z/538098.html

回到顶部