Python 元类编程实现一个简单的 ORM
概述
什么是ORM?
ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。
现在我们就要实现简易版ORM。
效果
class Person(Model):"""定义类的属性到列的映射"""pid = IntegerField('id')names = StringField('username')email = StringField('email')password = StringField('password')p = Person(pid=10086, names='晓明', email='10086@163.com', password='123456')
p.save()

通过执行save()方法 动态生成sql插入语句, 是不是很神奇, 那我们现在开始解析原理吧
步骤
首先我们要定义一个 Field 类 它负责保存数据库表的字段名和字段类型:
class Field(object):def __init__(self, name, column_type):self.name = nameself.column_type = column_typedef __str__(self):return '<%s:%s>' % (self.__class__.__name__, self.name)
在 Field 的基础上,进一步定义各种类型的 Field,比如 StringField,IntegerField 等等:
class StringField(Field):def __init__(self, name):super(StringField, self).__init__(name, 'varchar(100)')class IntegerField(Field):def __init__(self, name):super(IntegerField, self).__init__(name, 'bigint')
下一步,就是编写最复杂的 ModelMetaclass:
class ModelMetaclass(type):def __new__(cls, name, bases, attrs):if name == "Model":return type.__new__(cls, name, bases, attrs)mappings = dict()print("Found class: %s" % name)for k, v in attrs.items():if isinstance(v, Field):print("Found mapping: %s ==> %s" % (k, v))mappings[k] = vfor k in mappings.keys():attrs.pop(k)attrs["__table__"] = name # 表名和类名一致attrs["__mappings__"] = mappings # 保存属性和列的映射关系return type.__new__(cls, name, bases, attrs)
最后就是基类 Model:
class Model(metaclass=ModelMetaclass):def __init__(self, **kwargs):_setattr = setattrif kwargs:for k, v in kwargs.items():_setattr(self, k, v)super(Model, self).__init__()def save(self):fields = []params = []args = []for k, v in self.__mappings__.items():fields.append(k)params.append("?")args.append(getattr(self, k, None))sql = "insert into %s (%s) values (%s)" % (self.__table__, ','.join(fields), ",".join(params))print('插入语句: %s' % sql)print('参数: %s' % str(args))def update(self):fields = []args = []for k, v in self.__mappings__.items():if getattr(self, k, None):fields.append(k+"=?")args.append(getattr(self, k, None))sql = "update %s set %s" % (self.__table__, ','.join(fields))print("更新语句: %s " % sql)print("参数: %s" % args)def filter(self, *args):passdef delete(self):pass
当用户定义一个 class Person(Model) 继承父类时,Python解释器会在当前类 Person 的定义中找 __metaclass__,如果没有找到,就继续到父类中找 __metaclass__,实在找不到就用默认 type 类。
我们在父类 Model 中定义了 __metaclass__ 的 ModelMetaclass 来创建 Person 类,所以 metaclass 隐式地继承到子类。
在 ModelMetaclass 中,一共做了几件事情:
-
排除掉对
Model类的修改; -
在当前类(比如
Person)中查找定义的类的所有属性,如果找到一个 Field 属性,就把它保存到一个__mappings__的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误; -
把表名保存到
__table__中,这里简化为表名默认为类名。
在Model类中,就可以定义各种操作数据库的方法,比如save(),delete(),find(),update() 等等。
我们实现了save(), update()方法,把一个实例保存到数据库中。因为有表名,属性到字段的映射和属性值的集合,就可以构造出INSERT语句和UPDATE语句。
编写代码试试:
class UserInfo(Model):"""定义类的属性到列的映射"""uid = IntegerField('uid')name = StringField('username')email = StringField('email')password = StringField('password')class Person(Model):"""定义类的属性到列的映射"""pid = IntegerField('id')names = StringField('username')email = StringField('email')password = StringField('password')p = Person(pid=10086, names='晓明', email='10086@163.com', password='123456')
p.save()
u2 = UserInfo(password='123456')
u2.update()
输出
Found class: UserInfo
Found mapping: uid ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
Found class: Person
Found mapping: pid ==> <IntegerField:id>
Found mapping: names ==> <StringField:username>
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
插入语句: insert into Person (pid,names,email,password) values (?,?,?,?)
参数: [10086, '晓明', '10086@163.com', '123456']
更新语句: update UserInfo set password=?
参数: ['123456']
结束语
就这样一个小巧的ORM就这么完成了。是不是学到了很多呢 ?这里利用的是元编程,很多Python框架都运用了元编程达到动态操作类。
注:上述代码列子 结合了廖雪峰的列子和少量的django ORM源码。
完整代码
class Field(object):def __init__(self, name, column_type):self.name = nameself.column_type = column_typedef __str__(self):return '<%s:%s>' % (self.__class__.__name__, self.name)class StringField(Field):def __init__(self, name):super(StringField, self).__init__(name, 'varchar(100)')class IntegerField(Field):def __init__(self, name):super(IntegerField, self).__init__(name, 'bigint')class ModelMetaclass(type):def __new__(cls, name, bases, attrs):if name == "Model":return type.__new__(cls, name, bases, attrs)mappings = dict()print("Found class: %s" % name)for k, v in attrs.items():if isinstance(v, Field):print("Found mapping: %s ==> %s" % (k, v))mappings[k] = vfor k in mappings.keys():attrs.pop(k)attrs["__table__"] = name # 表名和类名一致attrs["__mappings__"] = mappings # 保存属性和列的映射关系return type.__new__(cls, name, bases, attrs)class Model(metaclass=ModelMetaclass):def __init__(self, **kwargs):_setattr = setattrif kwargs:for k, v in kwargs.items():_setattr(self, k, v)super(Model, self).__init__()def save(self):fields = []params = []args = []for k, v in self.__mappings__.items():fields.append(k)params.append("?")args.append(getattr(self, k, None))sql = "insert into %s (%s) values (%s)" % (self.__table__, ','.join(fields), ",".join(params))print('插入语句: %s' % sql)print('参数: %s' % str(args))def update(self):fields = []args = []for k, v in self.__mappings__.items():if getattr(self, k, None):fields.append(k+"=?")args.append(getattr(self, k, None))sql = "update %s set %s" % (self.__table__, ','.join(fields))print("更新语句: %s " % sql)print("参数: %s" % args)def filter(self, *args):passdef delete(self):passclass UserInfo(Model):"""定义类的属性到列的映射"""uid = IntegerField('uid')name = StringField('username')email = StringField('email')password = StringField('password')class Person(Model):"""定义类的属性到列的映射"""pid = IntegerField('id')names = StringField('username')email = StringField('email')password = StringField('password')p = Person(pid=10086, names='晓明', email='10086@163.com', password='123456')
p.save()
u2 = UserInfo(password='123456')
u2.update()
相关文章:
Python 元类编程实现一个简单的 ORM
概述 什么是ORM? ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。 现在我们就要实…...
《C++ Primer Plus》第18章:探讨 C++ 新标准(7)
C11 新增的其他功能 C11 增加了很多功能,本书无法全面介绍;另外,本书编写期间,其中很多功能还未得到广泛实现。然而,有些功能有必要简要地介绍一下。 并行编程 当前,为提高计算机性能,增加处…...
Redis学习(二):Redis安装测试
概述 Redis是什么 Redis, Remote Dictionary Server, 即远程字典服务。免费开源的数据库。 由C语言编写,支持网络,可基于内存亦可持久化的日志型、KV数据库,并提供所种语言的API。 Redis能干嘛 用于内存存储,持久化。rdb、ao…...
Vector - CAPL - 简介及数据结构
对于想进入车载行业或者已经在车载行业工作的朋友对于CAPL这个词都会相当的熟悉,都知道他是做车载网络测试脚本的语言,并且跟C有点类似,但是它到底是什么呢?CAPL全称(Communication Access Programming Language&#…...
20230304英语学习
What Would Happen if the Moon Disappeared Tomorrow? 如果明天月球消失了会怎样? The closest object to our planet, the Moon, may seem like Earth’s little sibling.Since its birth, the satellite has mostly just hung around, playing gravitational t…...
【基础算法】单链表的OJ练习(3) # 移除链表元素 # 相交链表 #
文章目录前言移除链表元素相交链表写在最后前言 本章的OJ练习也是相对简单的,只要能够理解解题的思路,并且依照这个思路能够快速的写出代码,我相信,你的链表水平已经足够了。 对于OJ练习(2) : ->传送门…...
【自用】SpringBoot项目通用类整理
文章目录全局Json序列化Controller日志切面全局异常拦截GlobalExceptionHandlerApiResultBusinessExceptionResponseEntityUtil全局返回体包装MP自动填充接口文档配置类自定义Async异步线程池本文主要整理各类项目中通用的配置类、工具类,便于复查自用。 全局Json序…...
动态规划法(总述)多阶段决策最优化问题
动态规划: 研究最优控制问题提出的 该问题有n个输入,问题的解由这n个输入组成,这个子集必须满足事先给定的条件,这些条件称为约束条件,满足约束条件的可行解可能不只有一个为了衡量可行解的优劣,通常以一些函数的形式&…...
MySQL跨服务器数据映射
MySQL跨服务器数据映射环境准备1. 首先是要查看数据库的federated引擎 开启/关闭 状态2. 打开任务管理器,并重启mysql服务3. 再次查看FEDERATED引擎状态,引擎已启动映射实现问题总结在日常的开发中经常进行跨数据库进行查询数据。 同服务器下跨数据库进…...
利用反射实现通过读取配置文件对类进行实例化-课后程序(JAVA基础案例教程-黑马程序员编著-第十二章-课后作业)
【案例12-3】:利用反射实现通过读取配置文件对类进行实例化 【案例介绍】 1.案例描述 现在有一个项目,项目中创建了一个Person类,在Person类中定义了一个sleep()方法。在工程中还定义了一个Student类继承Person类,在Student类中…...
1.2 CSS文本属性
CSS Text(文本)属性: 定义文本外观,颜色,装饰,缩进,行间距来修饰文本 文本样式 文本缩进 text-indent文本水平对齐方式:text-align文本修饰:text-decoration行高 line-height CSS文本颜色属性…...
SpringCloud之认识微服务
文章目录一、传统项目转型二、走进 SpringCloud三、微服务项目搭建3.1 创建一个 SpringBoot 项目3.2 创建三个 Maven 子工程3.3 为子工程创建 application.yml3.4 引入依赖3.5 数据库 建库建表3.6 编写业务提示:以下是本篇文章正文内容,SpringCloud系列学…...
【go语言之thrift协议二之server端分析】
go语言之thrift协议二serverthrift.TProtocolFactoryTTransportReadWriteCloserContextFlusherReadSizeProviderTProtocolrunServerNewTServerSocketNewCalculatorHandlerNewCalculatorProcessorNewTSimpleServer4server.ServeListenAcceptLoopprocessRequests在上一篇文章分析…...
【办公类05-03】Python批量修改文件名前面的序号(已有的序号错了,需要改成正确的号码)
背景需求下载教程,手动输入编号,有一个编号错误,导致后面所有编号都错了。30实际是29,以此类推怎样才能快速修改编号数字?前期考虑到可能要改编号,所以在每个编号后面加“ ”(空格)&…...
定向模糊测试工具Beacon基本用法
Beacon是一个定向模糊测试工具,给定行号,能够定向探索行号附近的代码区域。主要思想是采用静态分析的方法获取到与目标有关的变量的最弱前置条件(weakest precondition)的信息,并在相关位置插入断言,来提前…...
《程序员面试金典(第6版)》面试题 02.01. 移除重复节点
题目描述 编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。 示例1: 输入:[1, 2, 3, 3, 2, 1] 输出:[1, 2, 3] -示例2: 输入:[1, 1, 1, 1, 2] 输出:[1, 2] 提示: 链表长度在[0, 20000]范…...
如何对web系统开展无障碍测试
Accessibility test(无障碍测试)是一种测试方法,旨在评估软件、网站或其他数字产品的可访问性,以确保它们能够被身体残障或其他特殊需求的用户使用。这些测试通常包括使用辅助技术,如屏幕阅读器和放大器,以…...
使用vite+vue3.0 创建一个cesium基础应用 ----01 项目搭建
使用vitevue3.0 创建一个cesium基础应用 ----01 项目搭建 1.使用yarn创建一个vite项目 我们可以在vite官网找到vite创建项目的命令 https://cn.vitejs.dev/ 可以使用yarn创建项目选择使用vue3.0框架,语言使用js 创建完成后结构如下: 2.找到vite社区中的…...
【Python学习笔记】第二十七节 Python 多线程
一、进程和线程进程:是程序的一次执行,每个进程都有自己的地址空间、内存、数据栈及其他记录运行轨迹的辅助数据。线程:所有的线程都运行在同一个进程当中,共享相同的运行环境。线程有开始、顺序执行和结束三个部分, …...
【id:18】【20分】B. DS顺序表--连续操作
题目描述建立顺序表的类,属性包括:数组、实际长度、最大长度(设定为1000)该类具有以下成员函数:构造函数:实现顺序表的初始化。插入多个数据的multiinsert(int i, int n, int item[])函数,实现在…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
