Python 实现简易 ORM:元类与描述器详解
本文详细解析如何使用 Python 的元类和描述器从零构建一个简易 ORM 模型。通过自定义元类处理类创建与数据库表映射,利用描述器实现字段类型校验,帮助开发者深入理解 Flask 和 Django 等框架背后的 ORM 原理。
本文记录使用 Python 实现简易 ORM 模型的过程。
实现过程中涉及的核心知识包括元类和描述器。
首先介绍元类的概念。
其次介绍描述器的概念。
元类机制解析
我对元类的理解尚浅,其核心作用在于控制类的创建过程。
在 Python 3 中,所有类均由元类创建,且所有类的父类最终都是 object。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 全称为 Object Relational Mapping,即对象关系映射。
所谓对象关系映射,对象指的是什么?我的理解是:Python 中的类与数据库之间的映射关系。通过 ORM,开发者无需编写 SQL 语句,因为操作已被封装。例如,插入数据时只需创建一个对象即可。
Python 语言层面映射至数据库层面。
类名映射为数据库中的表名。
对象实例映射为数据库中的一行数据。
类的属性映射为数据库中的字段。
大致映射关系如上所述。
接下来介绍 ORM 的具体实现步骤。
第一步,利用描述器实现对数据库字段的类型和长度限制。
第二步,实现 Model 类,即 Python 中的模型类。
第三步,利用元类实现类与数据库表之间的映射关系。
首先,我们利用描述器来实现对数据字段的类型及长度限制。
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 模型即告完成。虽然在测试开发中较少直接手写 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)
以上就是如何使用 Python 实现一个简易 ORM 模型的详细内容,更多关于 Python 实现 ORM 模型的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













