Python下ORM的一个设计举例

来源:互联网 发布:dnd战士优化 编辑:程序博客网 时间:2024/06/10 14:36

 转自:http://blog.csdn.net/GVFDBDF/article/details/49254701




1、什么是ORM

对象关系映射(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是http://write.blog.csdn.net/postedit?ref=toolbar一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。

对象关系映射(Object-Relational Mapping)提供了概念性的、易于理解的模型化数据的方法。ORM方法论基于三个核心原则: 简单:以最基本的形式建模数据。 传达性:数据库结构被任何人都能理解的语言文档化。 精确性:基于数据模型创建正确标准化的结构。 典型地,建模者通过收集来自那些熟悉应用程序但不熟练的数据建模者的人的信息开发信息模型。建模者必须能够用非技术企业专家可以理解的术语在概念层次上与数据结构进行通讯。建模http://write.blog.csdn.net/postedit?ref=toolbar者也必须能以简单的单元分析信息,对样本数据进行处理。ORM专门被设计为改进这种联系。

对于我的简单理解就是在面向对象语言(主要是类和继承的运用)和关系型数据库(MySQL之类)之间建立映射关系,具体来说应该是一个类(比如user)对应一个数据库的表table(比如User表),使得运用面向对象的特点,通过封装继承类和实例来实现对关系型数据库的操作

2、举例分析


预备知识:Python协程和异步IO(yield from的使用)、SQL数据库操作、元类、面向对象知识、Python语法
参考:
异步IO和协程:http://blog.csdn.net/gvfdbdf/article/details/49254037
aiomysql参考文档:http://aiomysql.readthedocs.org/en/latest/connection.html
元类:http://www.cnblogs.com/ifantastic/p/3175735.html
类方法:http://blog.csdn.net/carolzhang8406/article/details/6856817

建立一个web访问的ORM,每一个web请求被连接之后都要接入数据库进行操作。在web框架中,采用基于asyncio的aiohttp,这是基于协程的异步模型,所以整个ORM的框架采用异步操作,采用aiomysql作为数据库的异步IO驱动。

思路分析:
Ⅰ. 首先需要建议一个全局的连接池,使得每一个HTTP请求都能从连接池中取得连接,然后接入数据库,这样就不会频繁的打开和关闭数据库

Ⅱ. 封装数据库操作函数(SELECT、INSERT、UPDATE、DELETE等)。每一个来自连接池的连接都可以通过生成游标的形式调用数据库操作函数,而这些操作函数是对数据库操作语句的封装。

Ⅲ. 封装数据库表中的每一列,定义Field类保存每一列的属性(包括数据类型,列名,是否为主键和默认值)

Ⅳ. 定义每一个数据库表映射类的元类ModelMetaclass,通过元类来控制数据库表映射的基类的生成。
ModelMetaclass的工作:一、读取具体子类(user)的映射信息(也就是User表)。二、在当前类中查找所有的类属性(attrs),如果找到Field属性,就将其保存到__mappings__的dict中,同时从类属性中删除Field(防止实例属性遮住类的同名属性)。三、将数据库表名保存到__table__中

Ⅴ. 定义ORM所有映射的基类:Model# Model类的任意子类可以映射一个数据库表。Model类可以看作是对所有数据库表操作的基本定义的映射,Model从dict继承,拥有字典的所有功能,同时实现特殊方法__getattr__和__setattr__,能够实现属性操作,实现数据库操作的所有方法,并定义为class方法,所有继承自Model都具有数据库操作方法。


[python] view plain copy
 print?
  1. # -*-coding:utf-8 -*-  
  2.   
  3. __auth__ = 'peic'  
  4.   
  5.   
  6. ''''' 
  7. 选择MySQL作为网站的后台数据库 
  8.  
  9. 执行SQL语句进行操作,并将常用的SELECT、INSERT等语句进行函数封装 
  10.  
  11. 在异步框架的基础上,采用aiomysql作为数据库的异步IO驱动 
  12.  
  13. 将数据库中表的操作,映射成一个类的操作,也就是数据库表的一行映射成一个对象(ORM) 
  14.  
  15. 整个ORM也是异步操作 
  16.  
  17. 预备知识:Python协程和异步IO(yield from的使用)、SQL数据库操作、元类、面向对象知识、Python语法 
  18.  
  19. # -*- -----  思路  ----- -*- 
  20.     如何定义一个user类,这个类和数据库中的表User构成映射关系,二者应该关联起来,user可以操作表User 
  21.      
  22.     通过Field类将user类的属性映射到User表的列中,其中每一列的字段又有自己的一些属性,包括数据类型,列名,主键和默认值 
  23.  
  24. '''  
  25.   
  26.   
  27.   
  28. import asyncio, logging  
  29.   
  30. # pip2 install aiomysql  
  31. import aiomysql  
  32.   
  33. # 打印SQL查询语句  
  34. def log(sql, args=()):  
  35.     logging.info('SQL: %s' %(sql))  
  36.   
  37.   
  38. # 创建一个全局的连接池,每个HTTP请求都从池中获得数据库连接  
  39. @asyncio.coroutine  
  40. def create_pool(loop, **kw):  
  41.     logging.info('create database connection pool...')  
  42.   
  43.     # 全局变量__pool用于存储整个连接池  
  44.     global __pool  
  45.     __pool = yield from aiomysql.create_pool(  
  46.             # **kw参数可以包含所有连接需要用到的关键字参数  
  47.             # 默认本机IP  
  48.             host = kw.get('host''localhost'),  
  49.             user = kw['user'],  
  50.             password = kw['password'],  
  51.             db = kw['db'],  
  52.             port = kw.get('port',3306),  
  53.             charset = kw.get('charset''utf8'),  
  54.             autocommit = kw.get('autocommit'True),  
  55.             # 默认最大连接数为10  
  56.             maxsize = kw.get('maxsize'10),  
  57.             minsize = kw.get('minisize'1),  
  58.               
  59.             # 接收一个event_loop实例  
  60.             loop = loop  
  61.             )  
  62.   
  63.   
  64. # 封装SQL SELECT语句为select函数  
  65. def select(sql, args, size=None):  
  66.     log(sql, args)  
  67.     global __pool  
  68.       
  69.     # -*- yield from 将会调用一个子协程,并直接返回调用的结果  
  70.     # yield from从连接池中返回一个连接  
  71.     with (yield from __pool) as conn:  
  72.         # DictCursor is a cursor which returns results as a dictionary  
  73.         cur = yield from conn.cursor(aiomysql.DictCursor)  
  74.           
  75.         # 执行SQL语句  
  76.         # SQL语句的占位符为?,MySQL的占位符为%s  
  77.         yield from cur.execute(sql.replace('?''%s'), args or ())  
  78.               
  79.         # 根据指定返回的size,返回查询的结果  
  80.         if size:  
  81.             # 返回size条查询结果  
  82.             rs = fetchmany(size)  
  83.         else:  
  84.             # 返回所有查询结果  
  85.             rs = fetchall()  
  86.   
  87.         yield from cur.close()  
  88.         logging.info('rows return: %s' %(len(rs)))  
  89.         return rs  
  90.   
  91.   
  92. # 封装INSERT, UPDATE, DELETE  
  93. # 语句操作参数一样,所以定义一个通用的执行函数  
  94. # 返回操作影响的行号  
  95. @asyncio.coroutine  
  96. def execute(sql, args):  
  97.     log(sql, args)  
  98.     global __pool  
  99.     with (yield from __pool) as conn:  
  100.         try:  
  101.             # execute类型的SQL操作返回的结果只有行号,所以不需要用DictCursor  
  102.             cur = yield from conn.cursor()  
  103.             cur.execute(sql.replace('?''%s'), args)  
  104.             affectedLine = cur.rowcount  
  105.             yield from cur.close()  
  106.         except BaseException as e:  
  107.             raise  
  108.         return affectedLine  
  109.   
  110.   
  111. # 根据输入的参数生成占位符列表  
  112. def create_args_string(num):  
  113.     L = []  
  114.     for n in range(num):  
  115.         L.append('?')  
  116.       
  117.     # 以','为分隔符,将列表合成字符串  
  118.     return (','.join(L))  
  119.   
  120.   
  121. # 定义Field类,负责保存(数据库)表的字段名和字段类型  
  122. class Field(object):  
  123.     # 表的字段包含名字、类型、是否为表的主键和默认值  
  124.     def __init__(self, name, column_type, primary_key, default):  
  125.         self.name = name  
  126.         self.column_type = column_type  
  127.         self.primary_key = primary_key  
  128.         self.default = default  
  129.       
  130.     # 当打印(数据库)表时,输出(数据库)表的信息:类名,字段类型和名字  
  131.     def __str__(self):  
  132.         return ('<%s, %s: %s>' %(self.__class__.__name__, self.column_type, self.name))  
  133.   
  134.   
  135.   
  136. # -*- 定义不同类型的衍生Field -*-  
  137. # -*- 表的不同列的字段的类型不一样  
  138.   
  139. class StringField(Field):  
  140.     def __init__(self, name=None, primary_key=False, default=None, column_type='varchar(100)'):  
  141.         super().__init__(name, column_type, primary_key, default)  
  142.   
  143. class BooleanField(Field):  
  144.     def __init__(self, name=None, default=None):  
  145.         super().__init__(name, 'boolean'False, default)  
  146.   
  147. class IntegerField(Field):  
  148.     def __init__(self, name=None, primary_key=False,  default=0):  
  149.         super().__init__(name, 'bigint', primary_key, default)  
  150.   
  151. class FloatField(Field):  
  152.     def __init__(self, name=None, primary_key=False,  default=0.0):  
  153.         super().__init__(name, 'real', primary_key, default)  
  154.           
  155. class TextField(Field):  
  156.     def __init__(self, name=None, default=None):  
  157.         super().__init__(name, 'Text'False, default)  
  158.   
  159.   
  160.   
  161.   
  162. # -*-定义Model的元类  
  163.   
  164. # 所有的元类都继承自type  
  165. # ModelMetaclass元类定义了所有Model基类(继承ModelMetaclass)的子类实现的操作  
  166.   
  167. # -*-ModelMetaclass的工作主要是为一个数据库表映射成一个封装的类做准备:  
  168. # ***读取具体子类(user)的映射信息  
  169. # 创造类的时候,排除对Model类的修改  
  170. # 在当前类中查找所有的类属性(attrs),如果找到Field属性,就将其保存到__mappings__的dict中,同时从类属性中删除Field(防止实例属性遮住类的同名属性)  
  171. # 将数据库表名保存到__table__中  
  172.   
  173. # 完成这些工作就可以在Model中定义各种数据库的操作方法  
  174.   
  175. class ModelMetaclass(type):  
  176.         
  177.     # __new__控制__init__的执行,所以在其执行之前  
  178.     # cls:代表要__init__的类,此参数在实例化时由Python解释器自动提供(例如下文的User和Model)  
  179.     # bases:代表继承父类的集合  
  180.     # attrs:类的方法集合  
  181.     def __new__(cls, name, bases, attrs):  
  182.           
  183.         # 排除Model  
  184.         if name == 'Model':  
  185.             return type.__new__(cls, name, bases, attrs)  
  186.           
  187.         # 获取table名词  
  188.         tableName = attrs.get('__table__'Noneor name  
  189.         logging.info('found model: %s (table: %s)' %(name, tableName))  
  190.           
  191.         # 获取Field和主键名  
  192.         mappings = dict()  
  193.         fields = []  
  194.         primaryKey = None         
  195.         for k,v in attrs.items():  
  196.             # Field 属性  
  197.             if isinstance(v, Field):  
  198.                 # 此处打印的k是类的一个属性,v是这个属性在数据库中对应的Field列表属性  
  199.                 logging.info('  found mapping: %s --> %s' %(k, v))  
  200.                 mappings[k] = v  
  201.                   
  202.                 # 找到了主键  
  203.                 if v.primary_key:  
  204.                       
  205.                     # 如果此时类实例的以存在主键,说明主键重复了                  
  206.                     if primaryKey:  
  207.                         raise StandardError('Duplicate primary key for field: %s' %k)  
  208.                     # 否则将此列设为列表的主键  
  209.                     primaryKey = k  
  210.                 else:  
  211.                     fields.append(k)  
  212.         # end for  
  213.   
  214.         if not primaryKey:  
  215.             raise StandardError('Primary key is nor founnd')  
  216.           
  217.         # 从类属性中删除Field属性  
  218.         for k in mappings.keys():  
  219.             attrs.pop(k)  
  220.   
  221.         # 保存除主键外的属性名为``(运算出字符串)列表形式  
  222.         escaped_fields = list(map(lambda f:'`%s`' %f, fields))  
  223.           
  224.         # 保存属性和列的映射关系  
  225.         attrs['__mappings__'] = mappings  
  226.         # 保存表名  
  227.         attrs['__table__'] = tableName  
  228.         # 保存主键属性名  
  229.         attrs['__primary_key__'] = primaryKey  
  230.         # 保存除主键外的属性名  
  231.         attrs['__fields__'] = fields  
  232.   
  233.         # 构造默认的SELECT、INSERT、UPDATE、DELETE语句  
  234.         # ``反引号功能同repr()  
  235.         attrs['__select__'] = 'select `%s`, %s from `%s`' %(primaryKey, ', '.join(escaped_fields), tableName)  
  236.         attrs['__insert__'] = 'insert into  `%s` (%s, `%s`) values(%s)' %(tableName, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields) + 1))  
  237.         attrs['__update__'] = 'update `%s` set `%s` where `%s` = ?' %(tableName, ', '.join(map(lambda f:'`%s`=?' %(mappings.get(f).name or f), fields)), primaryKey)  
  238.         attrs['__delete__'] = 'delete from  `%s` where `%s`=?' %(tableName, primaryKey)  
  239.               
  240.         return type.__new__(cls, name, bases, attrs)  
  241.   
  242.   
  243.   
  244.   
  245. # 定义ORM所有映射的基类:Model  
  246. # Model类的任意子类可以映射一个数据库表  
  247. # Model类可以看作是对所有数据库表操作的基本定义的映射  
  248.   
  249.   
  250. # 基于字典查询形式  
  251. # Model从dict继承,拥有字典的所有功能,同时实现特殊方法__getattr__和__setattr__,能够实现属性操作  
  252. # 实现数据库操作的所有方法,定义为class方法,所有继承自Model都具有数据库操作方法  
  253.   
  254. class Model(dict, metaclass=ModelMetaclass):  
  255.     def __init__(self, **kw):  
  256.         super(Model, self).__init__(**kw)  
  257.   
  258.     def __getattr__(self, key):  
  259.         try:  
  260.             return self[key]  
  261.         except KeyError:  
  262.             raise AttributeError(r'"Model" object has no attribute:%s' %(key))  
  263.   
  264.     def __setattr__(self, key, value):  
  265.         self[key] = value  
  266.   
  267.     def getValue(self, key):  
  268.         # 内建函数getattr会自动处理  
  269.         return getattr(self, key, None)  
  270.   
  271.     def getValueOrDefault(self, key):  
  272.         value = getattr(self, key, None)  
  273.         if not value:  
  274.             field = self.__mappings__[key]  
  275.             if field.default is not None:  
  276.                 value = field.default() if callable(field.default) else field.default  
  277.                 logging.debug('using default value for %s: %s' %(key, str(value)))  
  278.                 setattr(self, key, value)  
  279.         return value  
  280.  
  281.  
  282.  
  283.     @classmethod  
  284.     # 类方法有类变量cls传入,从而可以用cls做一些相关的处理。并且有子类继承时,调用该类方法时,传入的类变量cls是子类,而非父类。  
  285.     @asyncio.coroutine  
  286.     def findAll(cls, where=None, args=None, **kw):  
  287.         '''''find objects by where clause'''  
  288.         sql = [cls.__select__]  
  289.           
  290.         if where:  
  291.             sql.append('where')  
  292.             sql.append(where)  
  293.           
  294.         if args is None:  
  295.             args = []  
  296.           
  297.         orderBy = kw.get('orderBy'None)  
  298.         if orderBy:  
  299.             sql.append('order by')  
  300.             sql.append(orderBy)  
  301.   
  302.         limit = kw.get('limit'None)  
  303.         if limit is not None:  
  304.             sql.append('limit')  
  305.             if isinstance(limit, int):  
  306.                 sql.append('?')  
  307.                 args.append(limit)  
  308.             elif isinstance(limit, tuple) and len(limit) == 2:  
  309.                 sql.append('?,?')  
  310.                 args.extend(limit)  
  311.             else:  
  312.                 raise ValueError('Invalid limit value: %s' %str(limit))  
  313.         rs = yield from select(' '.join(sql), args)  
  314.         return [cls(**r) for r in rs]  
  315.  
  316.      
  317.     @classmethod  
  318.     @asyncio.coroutine  
  319.     def findNumber(cls, selectField, where=None, args=None):  
  320.         '''''find number by select and where.'''  
  321.         sql = ['select %s __num__ from `%s`' %(selectField, cls.__table__)]  
  322.         if where:  
  323.             sql.append('where')  
  324.             sql.append(where)  
  325.         rs = yield from select(' '.join(sql), args, 1)  
  326.         if len(rs) == 0:  
  327.             return None  
  328.         return rs[0]['__num__']  
  329.      
  330.  
  331.     @classmethod  
  332.     @asyncio.coroutine  
  333.     def find(cls, primarykey):  
  334.         '''''find object by primary key'''  
  335.         rs = yield from select('%s where `%s`=?' %(cls.__select__, cls__primary_key__), [primarykey], 1)  
  336.         if len(rs) == 0:  
  337.             return None  
  338.         return cls(**rs[0])  
  339.  
  340.     @asyncio.coroutine  
  341.     def save(self):  
  342.         args = list(map(self.getValueOrDefault, self.__fields__))  
  343.         args.append(self.getValueOrDefault(self.__primary_key__))  
  344.         rows = yield from execute(self.__insert__, args)  
  345.         if rows != 1:  
  346.             logging.warn('failed to insert record: affected rows: %s' %rows)  
  347.  
  348.     @asyncio.coroutine  
  349.     def update(self):  
  350.         args = list(map(self.getValue, self.__fields__))  
  351.         args.append(self.getValue(self.__primary_key__))  
  352.         rows = yield from execute(self.__updata__, args)  
  353.         if rows != 1:  
  354.             logging.warn('failed to update by primary key: affected rows: %s' %rows)  
  355.  
  356.     @asyncio.coroutine  
  357.     def remove(self):  
  358.         args = [self.getValue(self.__primary_key__)]  
  359.         rows = yield from execute(self.__updata__, args)  
  360.         if rows != 1:  
  361.             logging.warn('failed to remove by primary key: affected rows: %s' %rows)  
  362.   
  363.   
  364.   
  365. if __name__ == '__main__':  
  366.       
  367.     class User(Model):  
  368.         # 定义类的属性到列的映射:  
  369.         id = IntegerField('id',primary_key=True)  
  370.         name = StringField('username')  
  371.         email = StringField('email')  
  372.         password = StringField('password')  
  373.   
  374.     # 创建一个实例:  
  375.     u = User(id=12345, name='peic', email='peic@python.org', password='password')  
  376.     print(u)  
  377.     # 保存到数据库:  
  378.     u.save()  
  379.     print(u)  


运行结果:
[python] view plain copy
 print?
  1. <span style="font-size:12px;">root@ming:~/Desktop/pip-7.1.2# python3 ../ORM.py   
  2. {'email''peic@python.org''name''peic''password''password''id'12345}  
  3. {'email''peic@python.org''name''peic''password''password''id'12345}</span>

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 思想晚熟的人怎么办 孩子学不好数学怎么办 做作业速度慢怎么办 孩子碎头发太多怎么办 孩子碎发太多怎么办 孩子的作业太多怎么办 爸爸是乙肝孩子怎么办 孩子出生没有奶怎么办 顺产后没有奶水怎么办 还在经常逃课怎么办 老板不重视自己怎么办 孩子不听话爱撒谎怎么办 老师针对我孩子怎么办 小孩动不动就哭怎么办 孩子太拧应该怎么办 孩子贪玩不爱学习怎么办 高二了还不学怎么办 小朋友不喜欢上幼儿园怎么办 一岁半的宝宝不听话怎么办 老公是个窝囊废怎么办 幼儿园老师不喜欢家长怎么办 老师讨厌学生了怎么办 一个舍友很讨厌怎么办 家长对老师不满怎么办 教师被家长辱骂怎么办 如果老师喜欢自己怎么办 孩子不思进取逃避学习怎么办 老师拿孩子泄愤怎么办 老师对小孩不好怎么办 孩子被老师骂怎么办 孩子幼儿园被打怎么办 两个家长吵架老师怎么办 家长和老师矛盾怎么办 孩子特别害怕老师怎么办? 老师排挤孤立孩子怎么办 很害怕一件事怎么办 高中老师误会我怎么办 家长讹老师老师怎么办 小孩子老说头疼怎么办 孩子总是否定自己怎么办 孩子总是否定别人怎么办