首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

使用Python编写简单ORM

ORM(Object Relational Mapping)对象关系映射

很多web框架中都有ORM,它的核心作用有两点

将对象映射成SQL语句,一个对象对应数据库中的一张表,对象中的属性对应表中的字段

通过对象的形式让框架内部进行相应SQL语句的转换,一定程度上避免的SQL注入攻击

当然如果web使用了ORM,那么它就会比使用原始的SQL语句慢,因为多了一层将对象转换未SQL的过程

我们可以使用Python中的元类来实现一个简单的ORM框架,简单看一下Django ORM的源码,发现它也是通过python元类来实现的

自顶向下设计

在程序设计中,有种设计思想叫自顶向下进行设计,从顶部开始思考ORM框架如何实现。那么一个框架的最顶部就是用户如何使用,所以我们编写一段用户使用ORM框架的代码,假定用户这样使用我们的编写的ORM框架,ORM框架中要实现什么内容。

假定用户通过上面的代码使用我们的ORM框架

他定义了一个类User,继承了Model类(Model类是ORM框架提供的基类),在User类中,嵌套了Meta类,表明User类对应到数据库中表的表名

User类定义完后,他希望实例化User类后,可以直接调用save()方法对实例化时传入的数据进行保存

接着就来逐步实现ORM框架,让上面的代码可以正常使用数据库

定义IntegerField类和StringField类

先来定义上面代码中使用的IntegerField类和StringField类,看回上妈的使用代码

IntegerField类表示在数据库中创建bigint类型的字段,字段名使用变量名,这里就是idStringField类表示在数据库中创建varchar类型的字段,因为varchar类型的字段在创建时需要指明字段长度,所有我们希望可以使用max_length参数来表示varchar字段的长度,我们还希望可以通过defalue来设置varchar字段的默认值

下面开写,先定义出所有xxxxField类的基类Field,在Field类中定义出columntype属性表示数据库中字段的类型,maxlength属性表示数据库中字段的长度,default表示字段的默认值

定义好了基类后再定义出StringField类和IntegerField类就比较简单了,其实就是多态,不同处在于columntype属性和maxlength不同

定义ModelMetaclass类和Model类

在上面使用代码中,User类继承了Model类

按照逻辑,接下来,就要实现Model类了,但在实现Model类前,我们先要实现ModelMetaclass这个元类,如果你忘记了元类相关的知识,可以看一下python元类

ModelMetaclass元类的作用就是定义了Model类或Model子类(User类)的创建行为

当Model类要创建时,触发了if name=='Model'下面的代码

当User类(Model子类)要创建时,就会执行除if name=='Model'外的代码

ModelMetaclass类的具体代码如下:

上面代码的逻辑比较简单,从attrs中获得相应的属性,将这些属性存到mappings这个dict中

然后通过pop方法将attrs中已经加到mappings这个dict中的值删除,元类的作用是创建类,从上图也可以看出dict中id、email这些key对应的值其实都是内存地址,删除attrs中值的目的是避免后面使用getattrs()方法出现意想不到的错误

实现好了ModelMetaclass元类,接着就可以写Model类了

上面代码的大致逻辑就是从mappings中取出相应的值,通过getattr获得类中key对应的值,然后构建出一个sql语句

简单说一下getattr()方法:

当 u = User(id=234, name='ayuliao',email='123',password='123456') 这行代码被执行时,因为User是Model的子类,而Model类又绑定了ModelMetaclass元类,所用User在实例化时,ModelMetaclass元类中的_new_方法先与Model类的_init_方法被调用。

在ModelMetaclass元类中,使用了attrs.pop()方法删除attrs中相应的值,这是因为

1.当u.save()被执行时,在save()方法中getattr()会先从类的属性或父类的属性中找相应的值

2.当在类的属性或父类的属性中找不到相应的值时,才会通过_gettattrs_方法来查找

3.如果没用使用attrs.pop()方法将id、name、email、password这些关键字删除,getattr()方法可以直接从类中获得相应的值,这些值在上面也提及了,都是内存地址,而不是我们需要的具体数值

4.所以使用attrs.pop()删除那些关键字,这样就会调用_gettattrs_方法来查找,在_gettattrs_方法中,使用了self[item],因为类的作用是创建实例对象,所以这里的self其实就是User类的实例u,通过实例就可以获得 u = User(id=234, name='ayuliao',email='123',password='123456')这行代码中传入的值

到这里就将一个简单的ORM编写完成了,将上面的代码都放在一个python文件中,运行起来,效果如下

获得具体的SQL后,就可以调用Python链接数据库的库,将这段SQL作为输入,在数据库中创建相应的表,插入相应的字段,而这个过程对用户来说都是透明的,它只见到的定义了一个User类

为什么要使用元类?

仔细观察上面ModelMetaclass类和Model类的代码,感觉ModelMetaclass类的作用似乎就是将User类中定义的元素存到mappings这个dict中,然后赋值给attrs['mappings'],接着就让Model类来使用了。

为什么要让User类中的元素过一遍mappings这个dict呢?

删除元类,似乎也可以实现需求,代码如下:

运行结果:

删去了元类,依旧达到了效果,似乎还简单了很多

如果使用上面的代码来实现ORM框架就失去了ORM框架的意义了,代码中User类完全就是一个壳,完全可以将User类中的内容删除,改成pass,代码照样正常运行,其实也就说明,SQL语句的实现完全取决于实例化User类时传入的参数,这份代码中的Field类其实没啥用处,而一个正常的ORM框架一般都会使用Field类做参数检查、非法参数过滤

小结

可以去翻看一下Django ORM源码相关的博客、文章,理解真正实用的ORM框架在编写时要考虑哪些细节,本章只是实习了一个非常简易的ORM,要让ORM可以在真实项目中稳定运行,还是要多学点,共勉!

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180416G13H5M00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券