爬虫之数据神器10---Peewee实现ORM的核心原理

前言:

继续上一篇:爬虫之数据神器9---Peewee集成Django/Flask框架详解-CSDN博客

本章主要讲一些原理方面的东西,帮助大家在项目中 可以更好的理解!

正文:

一、模型定义

在Peewee中,模型的定义是通过模型元类(ModelMetaclass)实现的。Peewee利用Python的元类机制,在模型类定义中使用特殊的元类来创建模型类。下面我们将详细介绍模型元类的实现原理。

1.1 模型元类的实现

在Peewee中,模型元类(ModelMetaclass)是所有模型类的元类,它负责根据字段定义创建模型类属性。下面是一个简单的模型元类的实现示例:

class ModelMetaclass(type):def __new__(cls, name, bases, attrs):fields = {}# 遍历模型类的属性for key, value in attrs.items():# 判断是否为字段类型if isinstance(value, Field):fields[key] = value# 删除原属性,将字段添加到fields属性中for key in fields.keys():del attrs[key]attrs['__fields__'] = fields# 创建新的模型类return super().__new__(cls, name, bases, attrs)

在这个示例中,我们通过遍历模型类的属性,判断是否为字段类型,然后将字段添加到一个名为__fields__的字典中。最后,我们删除原属性,并将__fields__属性添加到模型类中。

1.2 字段描述符

在Peewee中,每个模型字段(Field)采用描述符(Descriptor)实现。描述符是一个包含__get____set__方法的类,它可以用于控制属性的访问和修改。在模型中,字段描述符用于在获取字段值时自动调用coerce转换函数,以及在设置字段值时自动调用内部逻辑进行校验。

以下是一个简单的字段描述符的实现示例:

class FieldDescriptor:def __init__(self, field_name):self.field_name = field_namedef __get__(self, instance, owner):if instance is None:return selfvalue = getattr(instance, self.field_name)# 调用coerce转换函数,返回转换后的值return self.field.coerce(value)def __set__(self, instance, value):# 调用内部逻辑进行校验self.field.validate(value)setattr(instance, self.field_name, value)class Field:descriptor_class = FieldDescriptordef __init__(self, coerce=None, validate=None):self.coerce = coerceself.validate = validatedef __set_name__(self, owner, name):self.name = namedef __get__(self, instance, owner):if instance is None:return selfreturn getattr(instance, self.name)def __set__(self, instance, value):setattr(instance, self.name, value)

在上面的示例中,我们定义了一个FieldDescriptor类作为字段的描述符,并定义了一个Field类作为字段的基类。在Field类中,我们通过__get__方法获取字段值时调用coerce转换函数,并通过__set__方法设置字段值时调用内部逻辑进行校验。

通过使用字段描述符,我们可以在模型中实现自动的转换和校验逻辑,使得模型的使用更加方便和安全。

1.3 模型类的定义

有了模型元类和字段描述符的支持,我们可以定义模型类了。下面是一个使用Peewee框架定义的示例模型类:

class User(Model):id = IntegerField()username = CharField(max_length=50)email = EmailField()created_at = DateTimeField(default=datetime.now)class Meta:database = database_instance

在这个示例中,我们定义了一个名为User的模型类。通过在模型类中定义字段,我们可以指定每个字段的数据类型和验证规则。例如,id字段使用IntegerFieldusername字段使用CharFieldemail字段使用EmailFieldcreated_at字段使用DateTimeField。这些字段都通过字段描述符自动实现类型转换和校验的功能。

我们还可以通过模型类的Meta内部类指定数据库连接。在上面的示例中,我们将database_instance作为模型类的数据库连接。

通过使用模型元类和字段描述符,我们可以方便地定义模型类,并且模型类的实例可以直接与数据库交互,进行数据的查询、插入、更新和删除等操作。

1.4 模型类的使用

一旦我们定义了模型类,我们就可以使用它来进行数据库操作。下面是一些常见的模型类使用方法:

# 创建表
User.create_table()# 插入数据
user = User(username='John', email='john@example.com')
user.save()# 查询数据
users = User.select().where(User.username == 'John')
for user in users:print(user.username, user.email)# 更新数据
user.username = 'John Doe'
user.save()# 删除数据
user.delete_instance()

在上述代码中,我们首先使用create_table方法创建了数据库表。然后,我们通过创建模型类的实例来插入数据,使用select方法查询数据,使用属性来更新数据,并使用delete_instance方法删除数据。

通过模型类的方法和属性,我们可以方便地进行数据库操作,而不必直接与底层的SQL语句打交道。

综上所述,模型定义是Peewee实现ORM框架的核心原理之一。模型元类和字段描述符的实现使得模型类的定义和使用变得简洁而灵活,大大提高了开发效率和数据安全性。

二、Schema转换

在Peewee中,Schema转换是指将模型类转换为数据库表的描述信息和相应的SQL语句。这一过程涉及到模型类到Schema描述类的转换以及Schema描述类到SQL语句的生成。下面我们将详细介绍这一过程的实现原理。

2.1 模型转换Schema

根据模型类生成Schema描述类是Schema转换的关键步骤之一。Peewee利用模型元类中的字段定义和元信息(如数据库连接、表名等)来创建Schema描述类。下面是一个简单的示例:

class ModelMetaclass(type):def __new__(cls, name, bases, attrs):fields = {}# 遍历模型类的属性for key, value in attrs.items():# 判断是否为字段类型if isinstance(value, Field):fields[key] = value# 删除原属性,将字段添加到fields属性中for key in fields.keys():del attrs[key]attrs['__fields__'] = fields# 创建新的模型类model_class = super().__new__(cls, name, bases, attrs)# 创建对应的Schema描述类schema_class = type(name + 'Schema', (Schema,), {'model_class': model_class})return schema_class

在上述示例中,我们在模型元类中创建了一个新的Schema描述类,该描述类以模型类的名称加上Schema后缀命名。我们将模型类作为Schema描述类的model_class属性,以便之后生成SQL语句时可以依据模型类的信息。

通过模型转换Schema,我们可以根据模型类方便地生成相应的Schema描述类,这使得后续的SQL生成工作更加简单和直观。

2.2 Schema生成SQL

一旦我们有了Schema描述类,就可以利用它来生成相应的SQL语句。Peewee的Schema类提供了丰富的方法,可以生成创建表的SQL语句,以及查询、插入等操作的SQL语句。下面是一些示例:

# 创建表的SQL语句
sql_create_table = UserSchema.create_table()# 查询数据的SQL语句
sql_select = UserSchema.select().where(UserSchema.username == 'John')# 插入数据的SQL语句
user_data = {'username': 'John','email': 'john@example.com',
}
sql_insert = UserSchema.insert(user_data)

在上述示例中,我们通过调用Schema描述类的方法生成了对应的SQL语句。例如,使用create_table方法生成创建表的SQL语句,使用select方法生成查询数据的SQL语句,使用insert方法生成插入数据的SQL语句。

通过Schema生成SQL,我们可以解耦模型类和实际的数据库操作,将数据库相关的操作封装在Schema描述类中,使得代码更加清晰和可维护。

2.3 示例:模型转换Schema和生成SQL

为了更好地理解模型转换Schema和生成SQL的过程,我们通过一个示例来演示具体的操作。

假设我们有一个名为User的模型类,其中包含以下字段:id、username和email。我们想要将该模型类转换为Schema描述类,并生成相应的SQL语句。

首先,我们定义模型类User

class User(Model):id = IntegerField()username = CharField(max_length=50)email = CharField(max_length=100)class Meta:database = database_instance

然后,我们通过模型元类将模型类转换为Schema描述类UserSchema

class ModelMetaclass(type):def __new__(cls, name, bases, attrs):fields = {}# 遍历模型类的属性for key, value in attrs.items():# 判断是否为字段类型if isinstance(value, Field):fields[key] = value# 删除原属性,将字段添加到fields属性中for key in fields.keys():del attrs[key]attrs['__fields__'] = fields# 创建新的模型类model_class = super().__new__(cls, name, bases, attrs)# 创建对应的Schema描述类schema_class = type(name + 'Schema', (Schema,), {'model_class': model_class})return schema_class# 使用模型元类转换模型类为Schema描述类
UserSchema = ModelMetaclass('User', (Model,), {})

接下来,我们可以使用Schema描述类生成相应的SQL语句:

# 生成创建表的SQL语句
sql_create_table = UserSchema.create_table()
print(sql_create_table)# 生成插入数据的SQL语句
user_data = {'username': 'John','email': 'john@example.com',
}
sql_insert = UserSchema.insert(user_data)
print(sql_insert)# 生成查询数据的SQL语句
sql_select = UserSchema.select().where(UserSchema.username == 'John')
print(sql_select)

运行以上代码,我们可以得到生成的SQL语句的示例输出:

CREATE TABLE "user" ("id" INTEGER, "username" VARCHAR(50), "email" VARCHAR(100))INSERT INTO "user" ("username", "email") VALUES ('John', 'john@example.com')SELECT * FROM "user" WHERE "username" = 'John'

从上述输出中,我们可以看到通过Schema描述类生成的SQL语句,包括创建表的语句、插入数据的语句以及查询数据的语句。

三、查询执行

在前面的章节中,我们已经介绍了查询的构造过程,包括通过链式调用构建查询树和支持逐步添加查询条件、联表查询等操作。在本章中,我们将深入讨论查询的执行过程。

3.1 查询构造过程

查询构造过程是利用Peewee库提供的方法来逐步构建查询条件和联表查询的过程。我们可以创建一个查询对象,然后通过链式调用方法来添加查询条件和关联模型等操作。

示例代码:

from peewee import *# 定义模型
database = SqliteDatabase(':memory:')class BaseModel(Model):class Meta:database = databaseclass User(BaseModel):username = CharField()age = IntegerField()class Article(BaseModel):title = CharField()content = TextField()author = ForeignKeyField(User, backref='articles')database.create_tables([User, Article])# 构造查询条件和关联查询
query = User.select().where(User.age >= 18)
query = query.join(Article).where(Article.title == 'Python')# 执行查询
result = query.execute()# 处理查询结果
for row in result:print(row.username, row.age)

在上述示例中,我们首先定义了两个模型 UserArticle,分别表示用户和文章。然后,我们创建了一个查询对象 query,通过调用 select() 方法来选择要查询的模型。

随后,我们通过 where() 方法添加了一个查询条件 User.age >= 18,并调用 join() 方法实现了模型之间的关联查询,即 UserArticle 之间的关系。然后,我们可以继续使用 where() 方法添加更多的查询条件,如 Article.title == 'Python'

最后,我们执行了查询并通过迭代结果来处理查询返回的数据。

3.2 查询执行

一旦我们完成了查询的构造,接下来就需要执行这个查询并获取结果。Peewee在查询的执行过程中会进行如下的转换和操作:

  1. 生成Cython查询表达式:Peewee会将查询树转换为Cython查询表达式,这样可以提高查询的性能和执行效率。通过使用Cython,Peewee可以将查询的逻辑转化为底层的原生SQL查询。

  2. 转换为原生SQL执行:生成的Cython查询表达式将被转换为原生SQL查询语句,然后由底层的数据库驱动程序执行。这样,我们可以充分利用数据库的功能和优化来执行查询。

  3. 将查询结果转化为模型对象:执行查询后,Peewee会将查询返回的结果转换为模型对象,这样我们可以方便地使用模型的属性和方法来操作查询结果。

示例代码:

from peewee import *# 定义模型,创建数据库等# 构造查询条件和关联查询# 执行查询
result = query.execute()# 处理查询结果
for row in result:# 处理查询结果的方式,如将结果转化为模型对象user = User.from_dict(row)print(user.username, user.age)

在上述示例中,我们执行了查询并将结果存储在 result 变量中。然后,我们可以使用适当的方式来处理查询结果。在示例中,我们将查询结果转化为模型对象,这样就可以使用模型的属性和方法来操作查询结果。

通过以上的示例和解释,我们详细介绍了查询的执行过程,包括查询构造和查询执行的关键步骤。Peewee的灵活链式调用和转换为底层原生SQL的机制,使得查询操作变得简单、高效且易于扩展。

总结:

本篇文章介绍了Peewee框架中的一些核心概念和实现原理。

  1. 首先介绍了模型元类的实现,该元类负责根据字段定义创建模型类属性,利用字段描述符实现字段的转换和校验逻辑。
  2. 然后示范了模型类的定义和使用方法,演示了创建表、插入数据、查询数据、更新数据和删除数据等常见操作。
  3. 接着讨论了模型转换Schema和生成SQL的过程,以及查询构造和查询执行的过程。通过这些内容的介绍和示例代码的演示,

大家可以了解到Peewee框架的使用方法和原理,以便更好地应用Peewee进行数据库操作。

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

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

相关文章

TiDB 实战分享丨第三方支付企业的核心数据库升级之路

本文介绍了一家第三方支付企业在面对市场竞争和监管压力的态势下,通过升级核心数据库来提升业务能力的实践。该企业选择 TiDB 分布式数据库,成功将其应用于核心业务、计费、清结算和交易查询等关键系统。TiDB 的水平扩展能力、高可用性和简化数据栈等优势…

electron打包Vue前端

Electron-Forge 打包Vue项目 效果:electronforge可将前端静态页面打包成.exe、.deb和.rpm等,能适配各种平台 示例:Windows环境下将前端 Vue 项目打包成exe文件 打包后的 exe 文件 运行 exe 文件 一、项目准备 开源项目 RouYi 下载 本…

码蹄集部分题目(第五弹;OJ赛2024年第10期)

🐋🐋🐋竹鼠通讯(钻石;分治思想;模板题:就算几何平面点对问题) 时间限制:3秒 占用内存:128M 🐟题目描述 在真空中,一块无限平坦光滑…

Python程序设计 闭包

1. 闭包 1.1 作用域 内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。全局名称(global names),模块中定义的名称&#xf…

自偏置电流镜、wilson和cascode电流镜、低压自偏置电流镜

1.自偏置电流镜 参考1:正确偏置和自启动电路 正确偏置: 2.自启动电路 参考2:两种自启动电路、cascode低压设计、自启动充放电过程分析 3.低压自偏置电流镜 参考3:电阻偏置分析 VbVgs3VodVgs1Vod 4.电阻偏置和MOS偏置的分…

【C++】动态规划算法

目录 还会持续更新动态规划斐波那契模型三步问题最小花费爬楼梯 路径问题不同路径路径问题Ⅱ 简单多状态按摩师 还会持续更新 动态规划 什么是动态规划? 斐波那契模型 三步问题 class Solution { public: const int N 1000009;int waysToStep(int n) {if(n1) …

Promise 与 async await 的区分同步与异步任务

由于JavaScript负责解释执行的主线程是单线程的,所以如果遇到耗时比较大的任务则会导致后面的任务阻塞。所以将任务分为了异步任务和同步任务,异步任务又被分为宏任务和微任务。异步任务可以解决阻塞的问题但也会带来执行的不确定性。异步任务也叫异步编…

服务器远程桌面连接不上怎么办?

随着互联网的发展和远程办公的兴起,服务器远程桌面连接成为了许多企业和个人不可或缺的工具。偶尔我们可能会碰到服务器远程桌面连接不上的情况,这时候我们需要找到解决办法,确保高效地远程访问服务器。 天联组网——突破远程连接障碍 在我们…

前端二维码工具小程序使用说明书

一、产品概述 前端二维码工具小程序是一款便捷、高效、易用的二维码生成与识别工具。本产品支持根据用户输入的文本或链接生成二维码,同时提供扫一扫功能以识别二维码内容,并支持将识别到的内容复制到剪贴板。此外,产品还提供了美化功能&…

Hadoop生态系统主要是什么?

Hadoop生态系统主要由以下几部分组成: Hadoop HDFS:这是Hadoop的核心组件之一,是一个用于存储大数据的分布式文件系统。它可以在廉价的硬件上提供高度的容错性,通过数据复制和故障切换实现数据的高可用性。 MapReduce&#xff1a…

Armadillo库:用于线性代数和科学计算的快速C/C++库下载及vs环境下的使用方法

armadillo库的下载及使用 一. armadillo库的下载二. vs2022环境下armadillo库的基本使用方法 一. armadillo库的下载 armadillo库的官方下载连接: https://arma.sourceforge.net/download.html 选择网页中最新版本的armadillo库压缩包文件进行下载。 解压armadillo-12.8.2.ta…

无重复的最长字串

📝个人主页:五敷有你 🔥系列专栏:算法分析与设计 ⛺️稳中求进,晒太阳 问题 给定一个字符串,我们需要找到该字符串中的最长无重复子串的长度。 示例 让我们以一个具体的示例来说明这个问题&#…

五_交换网络

交换机是局域网中最重要的设备,用于将同一网络中的多个设备连接起来。交换机基于MAC地址进行工作。本章主要介绍交换原理、转发方法、交换网络层次结构、交换机管理(SSH)和交换机安全等内容。 5.1 交换网络概述 5.1.1 交换机的工作原理 从传统概念来讲…

场景文本检测识别学习 day02(AlexNet论文阅读、ResNet论文精读)

怎么读论文 在第一遍阅读的时候,只需要看题目,摘要和结论,先看题目是不是跟我的方向有关,看摘要是不是用到了我感兴趣的方法,看结论他是怎么解决摘要中提出的问题,或者怎么实现摘要中的方法,然…

Elementplus 2.6.1表单校验模块开发体验改进

需求 之前的表单代码看了下,写的比较冗长,于是去万能的Github找点轮子,发现了这个: GitHub - aweiu/element-ui-verify: 如果你受够了饿了么ElementUI原生的校验方式,那就来试试它吧!一款更懂你的校验插件…

Taro框架中的H5 模板基本搭建

1.H5 模板框架的搭建 一个h5 的基本框架的搭建 基础template 阿乐/H5 Taro 的基础模板

Java多线程实战-从零手搓一个简易线程池(四)线程池生命周期状态流转实现

🏷️个人主页:牵着猫散步的鼠鼠 🏷️系列专栏:Java全栈-专栏 🏷️本系列源码仓库:多线程并发编程学习的多个代码片段(github) 🏷️个人学习笔记,若有缺误,欢迎评论区指正…

nexus设置s3存储

问题 因为我的nexus是安装在EC2上面,需要利用s3的存储能力,为nexus提供存储服务。 步骤 准备s3桶 输入桶名,创建s3桶,如下图: 创建桶读写策略 具体内容如下: {"Version": "2012-10-1…

c++之代码编译问题

为什么头文件不是编译的对象 1、头文件与包含指令(#include) 那些没有被项目中任何源文件包含的头文件,编译器是不去理会它的,不管它有没有语法错误,也不管它是否已添加到项目中。 2、包含指令的执行 包含指令是一种预编译指令,它…

如何区别进化和演化

在生物学中,"进化"和"演化"这两个词通常可以互换使用,它们都指的是生物种群随时间推移而发生的遗传变化。然而,在某些语境中,这两个词可能会有细微的差别: 进化(Evolution)…