什么是ORM?

ORM的英文全称是“Object Relational Mapping”,即对象-关系映射,从字面上直接理解,就是把“关系”给“对象”化

对应到数据库,我们知道关系数据库(例如Mysql)的特征就是数据与数据之间存在各种各样的“关系”,这种“关系”是由Table(表)来维护和表现的。

ORM就是把关系数据库的一个"表"映射成一个"类",然后给"类"添加各种各样的方法(比如增删改查)

也就是说,我们要完成这样的设计:user可以根据实际需求(表的定义,比如包括哪些数据字段等)把通过ORM把"表"(table)设计成一个"类"(class),这就需要用到Metaclass的特性: 动态的创建类(class)

这样,写代码更简单,不用直接操作SQL语句。

要编写一个ORM框架,所有的类都只能动态定义,因为我们知道,在数据库使用过程中,Mysql的“表”是由User来创建的;对应过来,那么“表”对应的类也是应该由User来创建的。

让我们来尝试编写一个ORM框架。

编写底层模块的第一步,就是先把调用接口写出来。

先搞清楚user会如何使用这个ORM框架:使用这个ORM框架,创建一个Customer类来操作对应的数据库表customer,我们期待他写出这样的代码:

  1. class Customer(Model):
  2. # 定义类的属性到列的映射:
  3. id = IntegerField('id')
  4. name = StringField('username')
  5. email = StringField('email')
  6. password = StringField('password')
  7.  
  8. # 创建一个实例:
  9. u = Customer(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
  10. # 保存到数据库:
  11. u.save()

其中,父类Model和属性类型StringFieldIntegerField是由ORM框架提供的。显然,save()等方法应该由ORM提供,这样既安全又使得user不用重复定义这些方法。具体实现上,save()等全部由metaclass和基类Model自动完成。虽然metaclass和基类Model的编写会比较复杂,但ORM的使用者用起来却异常简单。

现在,我们就按上面的接口来实现该ORM。

首先搞清楚我们应该做的工作:

  • 1. 既然要使得user能够通过继承Model,实现动态的创建类,那么必然要在ModelMetaclass中实现动态类的创建(比如能实现类Cutomer中的定义的attr属性们)
  • 2. 在基类Model中实现save等方法

首先来定义Field类,它负责保存数据库表的字段名和字段类型:

  1. class Field(object):
  2.  
  3. def __init__(self, name, column_type):
  4. self.name = name
  5. self.column_type = column_type
  6.  
  7. def __str__(self):
  8. return '<%s:%s>' % (self.__class__.__name__, self.name)

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

  1. class StringField(Field):
  2.  
  3. def __init__(self, name):
  4. super(StringField, self).__init__(name, 'varchar(100)')
  5.  
  6. class IntegerField(Field):
  7.  
  8. def __init__(self, name):
  9. super(IntegerField, self).__init__(name, 'bigint')

下一步,就是编写最复杂的ModelMetaclass了:

  1. class ModelMetaclass(type):
  2.  
  3. def __new__(cls, name, bases, attrs):
  4. if name=='Model':
  5. return type.__new__(cls, name, bases, attrs)
  6. print('Found model: %s' % name)
  7. mappings = dict()
  8. for k, v in attrs.items():
  9. if isinstance(v, Field):
  10. print('Found mapping: %s ==> %s' % (k, v))
  11. mappings[k] = v
  12. for k in mappings.keys():
  13. attrs.pop(k)
  14. attrs['__mappings__'] = mappings # 保存属性和列的映射关系
  15. attrs['__table__'] = name # 假设表名和类名一致
  16. return type.__new__(cls, name, bases, attrs)

当用户定义一个class Customer(Model)时,Python解释器首先在当前类Customer的定义中查找metaclass,如果没有找到,就继续在父类Model中查找metaclass,找到了,就使用Model中定义的metaclassModelMetaclass来创建Customer类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。

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

  1. 排除掉对Model类的修改(肯定要避免user通过调用ModelMetaclass来修改我们定义好的基类Model);

  2. 在当前类(比如Customer)中查找user定义的类(也就是例子中的Customer)的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,同时从类(Customer)的属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性,也就是说保证Customer这个类里没有id,name等属性,从而不会影响Customer的实例的值; 这里之所以为了避免出现实例与类的同名属性,既避免运行时错误,是因为当出现类和实例的同名属性时,python会优先调用实例属性,如果没有的话则会调用类的属性,但是有时当实例属性不存在时,我们并不希望用户能调用到这个同名的类属性,比如当实例属性被删除时,再用相同的名字,就访问到了类属性了。因此才这么做);

  3. 把表名保存到__table__中,这里简化为表名默认为类名。

以及基类Model

  1. class Model(dict, metaclass=ModelMetaclass):
  2.  
  3. def __init__(self, **kw):
  4. super(Model, self).__init__(**kw)
  5.  
  6. def __getattr__(self, key):
  7. try:
  8. return self[key]
  9. except KeyError:
  10. raise AttributeError(r"'Model' object has no attribute '%s'" % key)
  11.  
  12. def __setattr__(self, key, value):
  13. self[key] = value
  14.  
  15. def save(self):
  16. fields = []
  17. params = []
  18. args = []
  19. for k, v in self.__mappings__.items():
  20. fields.append(v.name)
  21. params.append('?')
  22. args.append(getattr(self, k, None))
  23. sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
  24. print('SQL: %s' % sql)
  25. print('ARGS: %s' % str(args))

Model类中,就可以定义各种操作数据库的方法,比如save()delete()find()update等等。

我们实现了save()方法,把一个实例保存到数据库中。因为有表名,属性到字段的映射和属性值的集合,就可以构造出INSERT语句。

编写代码试试:

  1. u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
  2. u.save()

输出如下:

  1. Found model: User
  2. Found mapping: email ==> <StringField:email>
  3. Found mapping: password ==> <StringField:password>
  4. Found mapping: id ==> <IntegerField:uid>
  5. Found mapping: name ==> <StringField:username>
  6. SQL: insert into User (password,email,username,id) values (?,?,?,?)
  7. ARGS: ['my-pwd', 'test@orm.org', 'Michael', 12345]

可以看到,save()方法已经打印出了可执行的SQL语句,以及参数列表,只需要真正连接到数据库,执行该SQL语句,就可以完成真正的功能。

最终,我们实现了关系型数据库的ORM框架,user可以通过继承Model类,把各种各样的表写成对象,进行操作。

总结一下,通过ORM的这个例子,我们可以看出Metaclass的作用总结起来就三点:

1.   拦截类的创建

2.   修改类

3.   返回修改之后的类

参考链接:

深刻理解Python中的元类(metaclass)

廖雪峰 Python 教程 使用元类

谈谈Python中元类Metaclass(二):ORM实践的更多相关文章

  1. 谈谈Python中元类Metaclass(一):什么是元类

    简单的讲,元类创建了Python中所有的对象. 我们说Python是一种动态语言,而动态语言和静态语言最大的不同,就是函数和类不是编译时定义的,而是运行时动态创建的. 比方说我们要定义一个HelloW ...

  2. python中元类(metaclass)的理解

    原文地址:http://www.cnblogs.com/tkqasn/p/6524879.html 一:类也是对象 类就是一组用来描述如何生成一个对象的代码. 类也是一个对象,只要你使用关键字clas ...

  3. python中的类(二)

    python中的类(二) 六.类的成员 字段:普通字段,静态字段 eg: class Province(): country=’中国’ #静态字段,保存在类中,执行时可以通过类或对象访问 def __ ...

  4. Python中元类

    元类(metaclass) 简单地说,元类就是一个能创建类的类,而类class 是由type创建的,class可以创建对象 type与object的关系详见:python中type和object 1. ...

  5. 对python中元类的理解

    1. 类也是对象 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍然成立: >>> class ObjectCreator(object): ...

  6. python基础----元类metaclass

    1 引子 class Foo: pass f1=Foo() #f1是通过Foo类实例化的对象 python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载cl ...

  7. python 通过元类控制类的创建

    一.python中如何创建类? 1. 直接定义类 class A: a = 'a' 2. 通过type对象创建 在python中一切都是对象 在上面这张图中,A是我们平常在python中写的类,它可以 ...

  8. [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式

    使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...

  9. 深刻理解Python中的元类(metaclass)以及元类实现单例模式

    在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍 ...

随机推荐

  1. Codeforces Round #327 (Div. 2) B Rebranding(映射)

    O(1)变换映射,最后一次性替换. #include<bits/stdc++.h> using namespace std; typedef long long ll; ; char s[ ...

  2. 待解决问题:c++栈对象的析构、虚拟内存与内存管理的关系、内存管理的解决方案。

    待解决问题:c++栈对象的析构.虚拟内存与内存管理的关系.内存管理的解决方案.

  3. 【洛谷5113】Sabbat of the witch(毒瘤分块)

    点此看题面 大致题意: 给你一个序列,要你支持三种操作:区间赋值,区间求和,撤回之前任一区间赋值操作. 分块 这道题应该是一道十分毒瘤的分块题. 这道题要用到的算法并不是很难,但是思维难度是真的高. ...

  4. ElasticSearch High Level REST API【3】Scroll 滚屏

    ES中提供了 FROM/SIZE 分页,但这种分页有性能瓶颈. Scroll会以间隔时间滚屏的方式返回全部的查询数据,可以作为数据量很大的情况下,分页的一个替代方案 完整的示例如下: public v ...

  5. git提交时报错 permission denied

    git push 时报错:permission denied xxx 目前很多解决办法是生成公钥和秘钥,这种方法安全可靠,比较适用于一台电脑对应一个git账户,但是多个账户在同一台电脑上提交使用git ...

  6. 【Django】Django开发中的日志输出

    开发环境:Ubuntu16.04+Django 1.11.9+Python2.7 一:使用自定义函数输出日志到log文件: import time def print_log(log): file_o ...

  7. linux下/dev/null被误删

    /dev/null文件是一个特殊的设备文件,可以用于清空一些日志文件,或者是使一些信息输出到此文件,用以节省硬盘空间.如果该空文件/dev/null文件被误删除掉, 如何再使用系统命令重新创建并设置该 ...

  8. Linux-WebServer安装和配置

    Apache 基本操作 解释 命令 安装 yum install httpd 启动 service httpd start 停止 service httpd stop 启动完成后 查看进程是否存在:p ...

  9. nodeJS 服务端文件上传

    var http = require('http'); var path = require('path'); var fs = require('fs'); function uploadFile( ...

  10. Table 分页处理

    介绍两种table分页处理:PHP分页 和 js(jquery.table)分页. 一:jquery.table: 1:下载两个文件:table_jui.css 和 jquery.dataTables ...