Python 元类编程实现一个简单的 ORM

news2024/12/22 22:23:31

概述

什么是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 = name
        self.column_type = column_type
    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)

在 Field 的基础上,进一步定义各种类型的 Field,比如 StringFieldIntegerField 等等:

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] = v
        for 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 = setattr
        if 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):
        pass

    def delete(self):
        pass

当用户定义一个 class Person(Model) 继承父类时,Python解释器会在当前类 Person 的定义中找 __metaclass__,如果没有找到,就继续到父类中找 __metaclass__,实在找不到就用默认 type 类。

我们在父类 Model 中定义了 __metaclass__ 的 ModelMetaclass 来创建 Person 类,所以 metaclass 隐式地继承到子类。

在 ModelMetaclass 中,一共做了几件事情:

  1. 排除掉对 Model 类的修改;

  2. 在当前类(比如 Person )中查找定义的类的所有属性,如果找到一个 Field 属性,就把它保存到一个 __mappings__ 的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误;

  3. 把表名保存到 __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 = name
        self.column_type = column_type

    def __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] = v
        for 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 = setattr
        if 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):
        pass

    def delete(self):
        pass


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()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/386793.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

白盒测试重点复习内容

白盒测试白盒测试之逻辑覆盖法逻辑覆盖用例设计方法1.语句覆盖2.判定覆盖(分支覆盖)3.条件覆盖4.判定条件覆盖5.条件组合覆盖6.路径覆盖白盒测试之基本路径测试法基本路径测试方法的步骤1.根据程序流程图画控制流图2.计算圈复杂度3.导出测试用例4.准备测试用例5.例题白盒测试总…

简单介绍编程进制

十进制 十进制的位权为 10&#xff0c;比如十进制的 123&#xff0c;123 1 * 10 ^ 2 2 * 10 ^ 1 3 * 10 ^ 0。 二进制 二进制的位权为 2&#xff0c;比如十进制的 4&#xff0c;二进制为 100&#xff0c;4 1 * 2 ^ 2 0 * 2 ^ 1 0 *2 ^ 0。 Java7 之前&#xff0c;不支…

【PyTorch】教程:torch.nn.Hardshrink

torch.nn.Hardshrink CLASS torch.nn.Hardshrink(lambd0.5) 参数 lambd ([float]) – the λ\lambdaλ 默认为 0.5 定义 HardShrink(x){x,if x>λx,if x<−λ0,otherwise \text{HardShrink}(x) \begin{cases} x, & \text{ if } x > \lambda \\ x, & \text{…

1528. 重新排列字符串

1528. 重新排列字符串https://leetcode.cn/problems/shuffle-string/ 难度简单52收藏分享切换为英文接收动态反馈 给你一个字符串 s 和一个 长度相同 的整数数组 indices 。 请你重新排列字符串 s &#xff0c;其中第 i 个字符需要移动到 indices[i] 指示的位置。 返回重新…

Python写一个自动发送直播弹幕的工具,非常简单

哈喽大家好&#xff0c;今天给大家用Python整一个可以在直播间自动发弹幕的工具&#xff0c;来为喜欢的主播疯狂扣6 &#xff01; 事情原由昨晚回家&#xff0c;表弟在看LOL直播&#xff0c;看得我气不打一处来&#xff0c;差点就想锤他。 身为程序员的表弟&#xff0c;看直…

教师论文|科技专著管理系统

技术&#xff1a;Java、JSP等摘要&#xff1a;随着计算机和互联网技术的发展&#xff0c;社会的信息化程度越来越高&#xff0c;各行各业只有适应这种发展趋势&#xff0c;才能增强自己的适应能力和竞争能力&#xff0c;不断发展壮大。大学作为教育的基地&#xff0c;是社会进步…

Java 方法简介

如果需要经常做某一操作&#xff0c;则需要重复写类似的代码&#xff0c;比如查找某个数。此外&#xff0c;某些复杂的操作需要分成多个步骤进行&#xff0c;以便理解和维护。 为此&#xff0c;计算机引入了函数的概念&#xff0c;用来减少重复代码&#xff0c;分解复杂操作。…

【Servlet篇3】HttpServletResponse的常用方法

HttpServletResponse代表的是一个HTTP请求对应的响应。 在这一篇文章当中&#xff0c;已经提到了HTTP响应是由哪几部分组成的&#xff1a; 【网络原理7】认识HTTP_革凡成圣211的博客-CSDN博客HTTP抓包&#xff0c;Fiddler的使用https://blog.csdn.net/weixin_56738054/articl…

ChIP-seq 分析:数据与Peak 基因注释(10)

动动发财的小手&#xff0c;点个赞吧&#xff01; 1. 数据 今天&#xff0c;我们将继续回顾我们在上一次中研究的 Myc ChIPseq。这包括用于 MEL 和 Ch12 细胞系的 Myc ChIPseq。 可在此处[1]找到 MEL 细胞系中 Myc ChIPseq 的信息和文件可在此处[2]找到 Ch12 细胞系中 Myc ChIP…

UE5 报错记录

文章目录The following modules are missing or built with a different engine versionXXX could not be compiled. Try rebuilding from source manaually添加组件时提示未定义标识符或函数名法一方法二方法三The following modules are missing or built with a different e…

MediaTek 天玑 8000 5G移动平台详细参数

MediaTek 天玑 8000 移动平台 采用先进的 台积电 5nm 工艺&#xff0c;拥有出众的性能和能效&#xff0c;为高端智能手机用户提供出色的高帧率游戏和 5G 移动体验。 天玑 8000 采用了 MediaTek 诸多先进技术&#xff0c;内置 MediaTek Imagiq 780影像引擎、第五代 AI 处理器APU…

【存储】存储阵列结构

存储阵列结构存储是什么DAS&#xff08;Direct Attached Storage&#xff09;DAS遇到的挑战SAN&#xff08;Storage Area Network&#xff09;FC SAN与IP SAN比较SAN存储应用NAS&#xff08;Network Attached Storage&#xff09;NAS SAN DAS架构SAN与NAS存储的基本区别存储架构…

2023年湖北省建设厅特种工电工架子工怎么报考?甘建二告诉你

2023年湖北省建设厅特种工考试&#xff0c;本人来走过场&#xff0c;即可通过考试哟 2023年湖北省建设厅特种工工种&#xff1a;&#xff08;官方公布&#xff09; 建筑电工&#xff0c;建筑架子工&#xff0c;建筑起重信号司索工&#xff0c;建筑起重机械司机&#xff0c;塔式…

蓝桥杯 python datetime基础

datetime datetime对象可以用来表示精确的日期和时间&#xff0c;其实例化方法如下&#xff1a; import datetime today datetime.datetime(year2022,month9,day21)print(today)print(today.year)#返回datetime对象中的年份print(today.month)#返回datetime对象中的月份prin…

CentOS 7.9安装Zabbix 4.4《保姆级教程》

CentOS 7.9安装Zabbix 4.4一、配置一览二、环境准备设置Selinux和firewalld设置软件源1.配置ustc CentOS-Base源2.安装zabbix 4.4官方源3.安装并更换epel源4.清除并生成缓存三、安装并配置Zabbix Server安装zabbix组件安装php安装mariadb并创建数据库修改zabbix_server.conf设置…

volatile底层-CPU缓存一致性协议MESI

目录 volatile底层-CPU缓存一致性协议MESI CPU高速缓存&#xff08;Cache Memory&#xff09; 带有高速缓存的CPU执行计算的流程 目前流行的多级缓存结构 多核CPU多级缓存一致性协议MESI MESI协议缓存状态 MESI状态转换 多核缓存协同操作 ​编辑单核读取 双核读取 修…

Modelsim 操作结构和流程

用到的命令一般都写到.do文件中&#xff0c;使用脚本语言进行批量处理。Step 1: Map librariesStep 2: Compile the designStep 3: Optimize the design (OPTIONAL)Step 4: Load the design into the simulatorStep 5: Run the simulationStep 6: Debug the design Note: Desig…

10、DNS部署和安全(图文解析)~千锋

目录 概述 域名组成 监听端口 客户机的域名解析过程 DNS解析分类 DNS服务器搭建 概述 Domain Name Service 域名服务 作用&#xff1a;为客户机提供域名解释服务器 域名组成 域名组成概述 “www.sina.com.cn”是一个域名&#xff0c;从严格意义上讲”sina.com.cn”才…

CV学习笔记-Faster-RCNN

Faster R-CNN 文章目录Faster R-CNN1. 目标检测算法1.1 计算机视觉有五大应用1.2 目标检测任务1.3 目标检测算法概述2. 边框回归&#xff08;Bounding-Box regression&#xff09;2.1 IoU2.2 统计学中的指标2.3 边框回归3. Faster-RCNN网络3.1 Conv layers3.2 Region Proposal …

Python每日一练(20230304)

目录 1. 移除链表元素 ★ 2. K 个一组翻转链表 ★★★ 3. 三角形最小路径和 ★★ 1. 移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&…