Python有自己的DB-API,请参见:
问题:
查询的结果集通过 Cursor 对象的 fetch* 方法集访问,或者直接在 Cursor 上迭代,因为 Cursor 本身是可迭代的。但返回的结果集都使用 tuple 来表示一行,tuple 需要通过索引访问,例如:
- >>>cursor = db.cursor();
-
>>> # List all the seminars
-
>>>cursor.execute('select * from Seminars')
-
>>>cursor.fetchall()
-
[(4, 'Web Commerce', 300.0, 26), (1, 'Python Programming', 200.0, 15), (3, 'Socket Programming', 475.0, 7), (2, 'Intro to Linux', 100.0, 32), ]
通过索引访问存在危险,如果表的结构发生改变(在 select * 的情况下)或者 select 语句改了,很容易出错,例如有SQL:
- select first_name, last_name, age from person
Python 的访问代码差不多是这个样子:
- for row in cursor:
-
p = Person(firstName=row[0], lastName=row[1], age=row[2])
-
# Do something with p
如果 select 改动了,多取了性别出来(0 和 1 表示 男女),SQL 变成:
- select first_name, last_name, gender, age from person
可以看到 row[2] 不再是年龄,但程序不会出错,会把0/1当作年龄继续运行,会发生什么事?想想,你永远年轻,当然永远不能退休。
解决方案:
SQLAlchemy 这样的中间件有完整的解决方案,它使用预定义的类来包装结果集,或者使用一个字典来表示一行,可以通过索引或列名类访问,可以想见 row['age'] 没有上面的问题。SQLAlchemy 请参见:
如果只是需要数据库查询的话,在 Python 2.6+ 中有更轻量级的方案,只需几行代码,速度也不受影响。
namedtuple 是 Python 2.6+ 中提供的增强的 tuple。只要申明一下,就可以通过名字访问 tuple 中的数据:
- >>> # Basic example
-
>>> Point = namedtuple('Point', ['x', 'y'])
-
>>> p = Point(11, y=22) # instantiate with positional or keyword arguments
-
>>> p[0] + p[1] # indexable like the plain tuple (11, 22)
-
33
-
>>> x, y = p # unpack like a regular tuple
-
>>> x, y
-
(11, 22)
-
>>> p.x + p.y # fields also accessible by name
-
33
-
>>> p # readable __repr__ with a name=value style
-
Point(x=11, y=22)
namedtuple 请参见:
我们可以写一个函数如下:
- def refineDBResultSet(cursor, className = 'MuteRecord'):
-
'''
-
This method refined the result set returned by a DB cursor
-
Generates an new iterator derived from namedtuple
-
As namedtuple is introduced from python 2.6, so this method need python 2.6 or later.
-
@param curser: The DB cursor on which just executed a sql
-
@param className: The given name for the namedtuple
-
'''
-
fields = ','.join([f[0].lower() for f in cursor.description])
-
Mute = collections.namedtuple(className, fields)
-
return itertools.imap(Mute._make, cursor)
该函数接受一个 Cursor 和一个可选的 class 名字,返回一个 Iterator 的 Adapter (迭代器的适配器,有些拗口,可以看作另一个迭代器,只不过迭代出的数据做了转换)。那个 Mute._make 是新申明的 namedtuple 类的工厂函数,接受一个 tuple,输出相应的 namedtuple。正好被用来在适配器里转换数据。好了,来看看新的访问代码:
- for person in refineDBResultSet(cursor, 'Person'):
-
print(person.first_name)
-
print(person.last_name)
-
print(person.age)
-
# Do something with person
很简单,我们已经可以把一行看作一个Person对象来处理了,那个class名字'Person'可有可无。而且也不再有位置索引的困扰,即使select语句改变了,除非person.age没有了会报错,否则不会取错的。
阅读(2443) | 评论(0) | 转发(0) |