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[])函数,实现在…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...