Python下ORM的一个设计举例

本文探讨了Python中的ORM(对象关系映射),它是如何在编程语言中实现数据库操作的一种技术,使得开发者可以使用面向对象的方式来处理数据库交互。
摘要由CSDN通过智能技术生成

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都具有数据库操作方法。



# -*-coding:utf-8 -*-

__auth__ = 'peic'


'''
选择MySQL作为网站的后台数据库

执行SQL语句进行操作,并将常用的SELECT、INSERT等语句进行函数封装

在异步框架的基础上,采用aiomysql作为数据库的异步IO驱动

将数据库中表的操作,映射成一个类的操作,也就是数据库表的一行映射成一个对象(ORM)

整个ORM也是异步操作

预备知识:Python协程和异步IO(yield from的使用)、SQL数据库操作、元类、面向对象知识、Python语法

# -*- -----  思路  ----- -*-
    如何定义一个user类,这个类和数据库中的表User构成映射关系,二者应该关联起来,user可以操作表User
    
    通过Field类将user类的属性映射到User表的列中,其中每一列的字段又有自己的一些属性,包括数据类型,列名,主键和默认值

'''



import asyncio, logging

# pip2 install aiomysql
import aiomysql

# 打印SQL查询语句
def log(sql, args=()):
    logging.info('SQL: %s' %(sql))


# 创建一个全局的连接池,每个HTTP请求都从池中获得数据库连接
@asyncio.coroutine
def create_pool(loop, **kw):
    logging.info('create database connection pool...')

    # 全局变量__pool用于存储整个连接池
    global __pool
    __pool = yield from aiomysql.create_pool(
            # **kw参数可以包含所有连接需要用到的关键字参数
            # 默认本机IP
            host = kw.get('host', 'localhost'),
            user = kw['user'],
            password = kw['password'],
            db = kw['db'],
            port = kw.get('port',3306),
            charset = kw.get('charset', 'utf8'),
            autocommit = kw.get('autocommit', True),
            # 默认最大连接数为10
            maxsize = kw.get('maxsize', 10),
            minsize = kw.get('minisize', 1),
            
            # 接收一个event_loop实例
            loop = loop
            )


# 封装SQL SELECT语句为select函数
def select(sql, args, size=None):
    log(sql, args)
    global __pool
    
    # -*- yield from 将会调用一个子协程,并直接返回调用的结果
    # yield from从连接池中返回一个连接
    with (yield from __pool) as conn:
        # DictCursor is a cursor which returns results as a dictionary
        cur = yield from conn.cursor(aiomysql.DictCursor)
        
        # 执行SQL语句
        # SQL语句的占位符为?,MySQL的占位符为%s
        yield from cur.execute(sql.replace('?', '%s'), args or ())
            
        # 根据指定返回的size,返回查询的结果
        if size:
            # 返回size条查询结果
            rs = fetchmany(size)
        else:
            # 返回所有查询结果
            rs = fetchall()

        yield from cur.close()
        logging.info('rows return: %s' %(len(rs)))
        return rs


# 封装INSERT, UPDATE, DELETE
# 语句操作参数一样,所以定义一个通用的执行函数
# 返回操作影响的行号
@asyncio.coroutine
def execute(sql, args):
    log(sql, args)
    global __pool
    with (yield from __pool) as conn:
        try:
            # execute类型的SQL操作返回的结果只有行号,所以不需要用DictCursor
            cur = yield from conn.cursor()
            cur.execute(sql.replace('?', '%s'), args)
            affectedLine = cur.rowcount
            yield from cur.close()
        except BaseException as e:
            raise
        return affectedLine


# 根据输入的参数生成占位符列表
def create_args_string(num):
    L = []
    for n in range(num):
        L.append('?')
    
    # 以','为分隔符,将列表合成字符串
    return (','.join(L))


# 定义Field类,负责保存(数据库)表的字段名和字段类型
class Field(object):
    # 表的字段包含名字、类型、是否为表的主键和默认值
    def __init__(self, name, column_type, primary_key, default):
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default
    
    # 当打印(数据库)表时,输出(数据库)表的信息:类名,字段类型和名字
    def __str__(self):
        return ('<%s, %s: %s>' %(self.__class__.__name__, self.column_type, self.name))



# -*- 定义不同类型的衍生Field -*-
# -*- 表的不同列的字段的类型不一样

class StringField(Field):
    def __init__(self, name=None, primary_key=False, default=None, column_type='varchar(100)'):
        super().__init__(name, column_type, primary_key, default)

class BooleanField(Field):
    def __init__(self, name=None, default=None):
        super().__init__(name, 'boolean', False, default)

class IntegerField(Field):
    def __init__(self, name=None, primary_key=False,  default=0):
        super().__init__(name, 'bigint', primary_key, default)

class FloatField(Field):
    def __init__(self, name=None, primary_key=False,  default=0.0):
        super().__init__(name, 'real', primary_key, default)
        
class TextField(Field):
    def __init__(self, name=None, default=None):
        super().__init__(name, 'Text', False, default)




# -*-定义Model的元类

# 所有的元类都继承自type
# ModelMetaclass元类定义了所有Model基类(继承ModelMetaclass)的子类实现的操作

# -*-ModelMetaclass的工作主要是为一个数据库表映射成一个封装的类做准备:
# ***读取具体子类(user)的映射信息
# 创造类的时候,排除对Model类的修改
# 在当前类中查找所有的类属性(attrs),如果找到Field属性,就将其保存到__mappings__的dict中,同时从类属性中删除Field(防止实例属性遮住类的同名属性)
# 将数据库表名保存到__table__中

# 完成这些工作就可以在Model中定义各种数据库的操作方法

class ModelMetaclass(type):
      
    # __new__控制__init__的执行,所以在其执行之前
    # cls:代表要__init__的类,此参数在实例化时由Python解释器自动提供(例如下文的User和Model)
    # bases:代表继承父类的集合
    # attrs:类的方法集合
    def __new__(cls, name, bases, attrs):
        
        # 排除Model
        if name == 'Model':
            return type.__new__(cls, name, bases, attrs)
        
        # 获取table名词
        tableName = attrs.get('__table__', None) or name
        logging.info('found model: %s (table: %s)' %(name, tableName))
        
        # 获取Field和主键名
        mappings = dict()
        fields = []
        primaryKey = None       
        for k,v in attrs.items():
            # Field 属性
            if isinstance(v, Field):
                # 此处打印的k是类的一个属性,v是这个属性在数据库中对应的Field列表属性
                logging.info('  found mapping: %s --> %s' %(k, v))
                mappings[k] = v
                
                # 找到了主键
                if v.primary_key:
                    
                    # 如果此时类实例的以存在主键,说明主键重复了                
                    if primaryKey:
                        raise StandardError('Duplicate primary key for field: %s' %k)
                    # 否则将此列设为列表的主键
                    primaryKey = k
                else:
                    fields.append(k)
        # end for

        if not primaryKey:
            raise StandardError('Primary key is nor founnd')
        
        # 从类属性中删除Field属性
        for k in mappings.keys():
            attrs.pop(k)

        # 保存除主键外的属性名为``(运算出字符串)列表形式
        escaped_fields = list(map(lambda f:'`%s`' %f, fields))
        
        # 保存属性和列的映射关系
        attrs['__mappings__'] = mappings
        # 保存表名
        attrs['__table__'] = tableName
        # 保存主键属性名
        attrs['__primary_key__'] = primaryKey
        # 保存除主键外的属性名
        attrs['__fields__'] = fields

        # 构造默认的SELECT、INSERT、UPDATE、DELETE语句
        # ``反引号功能同repr()
        attrs['__select__'] = 'select `%s`, %s from `%s`' %(primaryKey, ', '.join(escaped_fields), tableName)
        attrs['__insert__'] = 'insert into  `%s` (%s, `%s`) values(%s)' %(tableName, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields) + 1))
        attrs['__update__'] = 'update `%s` set `%s` where `%s` = ?' %(tableName, ', '.join(map(lambda f:'`%s`=?' %(mappings.get(f).name or f), fields)), primaryKey)
        attrs['__delete__'] = 'delete from  `%s` where `%s`=?' %(tableName, primaryKey)
            
        return type.__new__(cls, name, bases, attrs)




# 定义ORM所有映射的基类:Model
# Model类的任意子类可以映射一个数据库表
# Model类可以看作是对所有数据库表操作的基本定义的映射


# 基于字典查询形式
# Model从dict继承,拥有字典的所有功能,同时实现特殊方法__getattr__和__setattr__,能够实现属性操作
# 实现数据库操作的所有方法,定义为class方法,所有继承自Model都具有数据库操作方法

class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r'"Model" object has no attribute:%s' %(key))

    def __setattr__(self, key, value):
        self[key] = value

    def getValue(self, key):
        # 内建函数getattr会自动处理
        return getattr(self, key, None)

    def getValueOrDefault(self, key):
        value = getattr(self, key, None)
        if not value:
            field = self.__mappings__[key]
            if field.default is not None:
                value = field.default() if callable(field.default) else field.default
                logging.debug('using default value for %s: %s' %(key, str(value)))
                setattr(self, key, value)
        return value



    @classmethod
    # 类方法有类变量cls传入,从而可以用cls做一些相关的处理。并且有子类继承时,调用该类方法时,传入的类变量cls是子类,而非父类。
    @asyncio.coroutine
    def findAll(cls, where=None, args=None, **kw):
        '''find objects by where clause'''
        sql = [cls.__select__]
        
        if where:
            sql.append('where')
            sql.append(where)
        
        if args is None:
            args = []
        
        orderBy = kw.get('orderBy', None)
        if orderBy:
            sql.append('order by')
            sql.append(orderBy)

        limit = kw.get('limit', None)
        if limit is not None:
            sql.append('limit')
            if isinstance(limit, int):
                sql.append('?')
                args.append(limit)
            elif isinstance(limit, tuple) and len(limit) == 2:
                sql.append('?,?')
                args.extend(limit)
            else:
                raise ValueError('Invalid limit value: %s' %str(limit))
        rs = yield from select(' '.join(sql), args)
        return [cls(**r) for r in rs]

    
    @classmethod
    @asyncio.coroutine
    def findNumber(cls, selectField, where=None, args=None):
        '''find number by select and where.'''
        sql = ['select %s __num__ from `%s`' %(selectField, cls.__table__)]
        if where:
            sql.append('where')
            sql.append(where)
        rs = yield from select(' '.join(sql), args, 1)
        if len(rs) == 0:
            return None
        return rs[0]['__num__']
    

    @classmethod
    @asyncio.coroutine
    def find(cls, primarykey):
        '''find object by primary key'''
        rs = yield from select('%s where `%s`=?' %(cls.__select__, cls__primary_key__), [primarykey], 1)
        if len(rs) == 0:
            return None
        return cls(**rs[0])

    @asyncio.coroutine
    def save(self):
        args = list(map(self.getValueOrDefault, self.__fields__))
        args.append(self.getValueOrDefault(self.__primary_key__))
        rows = yield from execute(self.__insert__, args)
        if rows != 1:
            logging.warn('failed to insert record: affected rows: %s' %rows)

    @asyncio.coroutine
    def update(self):
        args = list(map(self.getValue, self.__fields__))
        args.append(self.getValue(self.__primary_key__))
        rows = yield from execute(self.__updata__, args)
        if rows != 1:
            logging.warn('failed to update by primary key: affected rows: %s' %rows)

    @asyncio.coroutine
    def remove(self):
        args = [self.getValue(self.__primary_key__)]
        rows = yield from execute(self.__updata__, args)
        if rows != 1:
            logging.warn('failed to remove by primary key: affected rows: %s' %rows)



if __name__ == '__main__':
    
    class User(Model):
        # 定义类的属性到列的映射:
        id = IntegerField('id',primary_key=True)
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')

    # 创建一个实例:
    u = User(id=12345, name='peic', email='peic@python.org', password='password')
    print(u)
    # 保存到数据库:
    u.save()
    print(u)


运行结果:
<span style="font-size:12px;">root@ming:~/Desktop/pip-7.1.2# python3 ../ORM.py 
{'email': 'peic@python.org', 'name': 'peic', 'password': 'password', 'id': 12345}
{'email': 'peic@python.org', 'name': 'peic', 'password': 'password', 'id': 12345}</span>


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值