在Python中编写数据库模块的教程


Posted in Python onApril 29, 2015

在一个Web App中,所有数据,包括用户信息、发布的日志、评论等,都存储在数据库中。在awesome-python-app中,我们选择MySQL作为数据库。

Web App里面有很多地方都要访问数据库。访问数据库需要创建数据库连接、游标对象,然后执行SQL语句,最后处理异常,清理资源。这些访问数据库的代码如果分散到各个函数中,势必无法维护,也不利于代码复用。

此外,在一个Web App中,有多个用户会同时访问,系统以多进程或多线程模式来处理每个用户的请求。假设以多线程为例,每个线程在访问数据库时,都必须创建仅属于自身的连接,对别的线程不可见,否则,就会造成数据库操作混乱。

所以,我们还要创建一个简单可靠的数据库访问模型,在一个线程中,能既安全又简单地操作数据库。

为什么不选择SQLAlchemy?SQLAlchemy太庞大,过度地面向对象设计导致API太复杂。

所以我们决定自己设计一个封装基本的SELECT、INSERT、UPDATE和DELETE操作的db模块:transwarp.db。
设计db接口

设计底层模块的原则是,根据上层调用者设计简单易用的API接口,然后,实现模块内部代码。

假设transwarp.db模块已经编写完毕,我们希望以这样的方式来调用它:

首先,初始化数据库连接信息,通过create_engine()函数:

from transwarp import db
db.create_engine(user='root', password='password', database='test', host='127.0.0.1', port=3306)

然后,就可以直接操作SQL了。

如果需要做一个查询,可以直接调用select()方法,返回的是list,每一个元素是用dict表示的对应的行:

users = db.select('select * from user')
# users =>
# [
#   { "id": 1, "name": "Michael"},
#   { "id": 2, "name": "Bob"},
#   { "id": 3, "name": "Adam"}
# ]

如果要执行INSERT、UPDATE或DELETE操作,执行update()方法,返回受影响的行数:

n = db.update('insert into user(id, name) values(?, ?)', 4, 'Jack')

update()函数签名为:

update(sql, *args)

统一用?作为占位符,并传入可变参数来绑定,从根本上避免SQL注入攻击。

每个select()或update()调用,都隐含地自动打开并关闭了数据库连接,这样,上层调用者就完全不必关心数据库底层连接。

但是,如果要在一个数据库连接里执行多个SQL语句怎么办?我们用一个with语句实现:

with db.connection():
  db.select('...')
  db.update('...')
  db.update('...')

如果要在一个数据库事务中执行多个SQL语句怎么办?我们还是用一个with语句实现:

with db.transaction():
  db.select('...')
  db.update('...')
  db.update('...')

实现db模块

由于模块是全局对象,模块变量是全局唯一变量,所以,有两个重要的模块变量:

# db.py

# 数据库引擎对象:
class _Engine(object):
  def __init__(self, connect):
    self._connect = connect
  def connect(self):
    return self._connect()

engine = None

# 持有数据库连接的上下文对象:
class _DbCtx(threading.local):
  def __init__(self):
    self.connection = None
    self.transactions = 0

  def is_init(self):
    return not self.connection is None

  def init(self):
    self.connection = _LasyConnection()
    self.transactions = 0

  def cleanup(self):
    self.connection.cleanup()
    self.connection = None

  def cursor(self):
    return self.connection.cursor()

_db_ctx = _DbCtx()

由于_db_ctx是threadlocal对象,所以,它持有的数据库连接对于每个线程看到的都是不一样的。任何一个线程都无法访问到其他线程持有的数据库连接。

有了这两个全局变量,我们继续实现数据库连接的上下文,目的是自动获取和释放连接:

class _ConnectionCtx(object):
  def __enter__(self):
    global _db_ctx
    self.should_cleanup = False
    if not _db_ctx.is_init():
      _db_ctx.init()
      self.should_cleanup = True
    return self

  def __exit__(self, exctype, excvalue, traceback):
    global _db_ctx
    if self.should_cleanup:
      _db_ctx.cleanup()

def connection():
  return _ConnectionCtx()

定义了__enter__()和__exit__()的对象可以用于with语句,确保任何情况下__exit__()方法可以被调用。

把_ConnectionCtx的作用域作用到一个函数调用上,可以这么写:

with connection():
  do_some_db_operation()
但是更简单的写法是写个@decorator:
@with_connection
def do_some_db_operation():
  pass

这样,我们实现select()、update()方法就更简单了:

@with_connection
def select(sql, *args):
  pass

@with_connection
def update(sql, *args):
  pass

注意到Connection对象是存储在_DbCtx这个threadlocal对象里的,因此,嵌套使用with connection()也没有问题。_DbCtx永远检测当前是否已存在Connection,如果存在,直接使用,如果不存在,则打开一个新的Connection。

对于transaction也是类似的,with transaction()定义了一个数据库事务:

with db.transaction():
  db.select('...')
  db.update('...')
  db.update('...')

函数作用域的事务也有一个简化的@decorator:

@with_transaction
def do_in_transaction():
  pass

事务也可以嵌套,内层事务会自动合并到外层事务中,这种事务模型足够满足99%的需求。

事务嵌套比Connection嵌套复杂一点,因为事务嵌套需要计数,每遇到一层嵌套就+1,离开一层嵌套就-1,最后到0时提交事务:

class _TransactionCtx(object):
  def __enter__(self):
    global _db_ctx
    self.should_close_conn = False
    if not _db_ctx.is_init():
      _db_ctx.init()
      self.should_close_conn = True
    _db_ctx.transactions = _db_ctx.transactions + 1
    return self

  def __exit__(self, exctype, excvalue, traceback):
    global _db_ctx
    _db_ctx.transactions = _db_ctx.transactions - 1
    try:
      if _db_ctx.transactions==0:
        if exctype is None:
          self.commit()
        else:
          self.rollback()
    finally:
      if self.should_close_conn:
        _db_ctx.cleanup()

  def commit(self):
    global _db_ctx
    try:
      _db_ctx.connection.commit()
    except:
      _db_ctx.connection.rollback()
      raise

  def rollback(self):
    global _db_ctx
    _db_ctx.connection.rollback()

最后,把select()和update()方法实现了,db模块就完成了。

Python 相关文章推荐
使用Python脚本将绝对url替换为相对url的教程
Apr 24 Python
python自定义解析简单xml格式文件的方法
May 11 Python
详谈python http长连接客户端
Jun 12 Python
Python发送邮件功能示例【使用QQ邮箱】
Dec 04 Python
python的sorted用法详解
Jun 25 Python
使用WingPro 7 设置Python路径的方法
Jul 24 Python
python 函数嵌套及多函数共同运行知识点讲解
Mar 03 Python
python实现拼接图片
Mar 23 Python
Tensorflow中的降维函数tf.reduce_*使用总结
Apr 20 Python
什么是python的函数体
Jun 19 Python
Python文件操作及内置函数flush原理解析
Oct 13 Python
Python机器学习之逻辑回归
May 11 Python
Python的gevent框架的入门教程
Apr 29 #Python
在Python中使用HTML模版的教程
Apr 29 #Python
以Flask为例讲解Python的框架的使用方法
Apr 29 #Python
详解Python程序与服务器连接的WSGI接口
Apr 29 #Python
Python的SQLAlchemy框架使用入门
Apr 29 #Python
python使用post提交数据到远程url的方法
Apr 29 #Python
python实现根据ip地址反向查找主机名称的方法
Apr 29 #Python
You might like
用Flash图形化数据(二)
2006/10/09 PHP
Cakephp 执行主要流程
2010/03/24 PHP
fetchAll()与mysql_fetch_array()的区别详解
2013/06/05 PHP
php生成excel文件的简单方法
2014/02/08 PHP
PHP中捕获超时事件的方法实例
2015/02/12 PHP
php生成高清缩略图实例详解
2015/12/07 PHP
PHP实现带重试功能的curl连接示例
2016/07/28 PHP
php实现的生成迷宫与迷宫寻址算法完整实例
2017/11/06 PHP
给jqGrid数据行添加修改和删除操作链接(之一)
2011/11/04 Javascript
JavaScript定义类和对象的方法
2014/11/26 Javascript
javascript实现行拖动的方法
2015/05/27 Javascript
学习JavaScript设计模式之观察者模式
2020/04/22 Javascript
微信小程序 时间格式化(util.formatTime(new Date))详解
2016/11/16 Javascript
JS Select下拉框(支持输入模糊查询)
2017/02/04 Javascript
JS+canvas画一个圆锥实例代码
2017/12/13 Javascript
webpack-dev-server远程访问配置方法
2018/02/22 Javascript
JS实现的JSON数组去重算法示例
2018/04/11 Javascript
vue权限路由实现的方法示例总结
2018/07/29 Javascript
angular2/ionic2 实现搜索结果中的搜索关键字高亮的示例
2018/08/17 Javascript
layui-table表复选框勾选的所有行数据获取的例子
2019/09/13 Javascript
JavaScript实现多个物体同时运动
2020/03/12 Javascript
如何在Vue中使localStorage具有响应式(思想实验)
2020/07/14 Javascript
vuejs element table 表格添加行,修改,单独删除行,批量删除行操作
2020/07/18 Javascript
[01:14:55]EG vs Spirit Supermajor 败者组 BO3 第三场 6.4
2018/06/05 DOTA
Python爬虫模拟登录带验证码网站
2016/01/22 Python
python获取网页中所有图片并筛选指定分辨率的方法
2018/03/31 Python
Python动态生成多维数组的方法示例
2018/08/09 Python
python 多线程中子线程和主线程相互通信方法
2018/11/09 Python
python实现一组典型数据格式转换
2018/12/15 Python
详解Python字符串切片
2019/05/20 Python
关于Python核心框架tornado的异步协程的2种方法详解
2019/08/28 Python
django的模型类管理器——数据库操作的封装详解
2020/04/01 Python
使用SVG实现提示框功能的示例代码
2020/06/05 HTML / CSS
Shopee马来西亚:随拍即卖,最佳行动电商拍卖平台
2017/06/05 全球购物
求职信怎么写范文
2014/05/26 职场文书
python之django路由和视图案例教程
2021/07/26 Python