在Python中处理数据库结果集的最佳做法?
我正在编写一个简单的Python Web应用程序,其中包含为iPhone格式化的几页业务数据。我对Python编程很满意,但是对Python的"惯用语"不是很熟悉,尤其是在类和对象方面。 Python的面向对象设计与我使用过的其他语言有些不同。因此,即使我的应用程序正在运行,我也很好奇是否有更好的方法可以实现我的目标。
细节:通常如何在Python中实现请求转换呈现数据库工作流?当前,我正在使用pyodbc来获取数据,将结果复制到对象的属性中,使用这些对象的列表执行一些计算和合并,然后从对象列表中呈现输出。 (下面的示例代码,已删除的SQL查询。)这是理智的吗?有没有更好的办法?我相对不了解Python时会偶然发现任何特定的"陷阱"吗?我特别担心如何使用空的" Record"类实现行列表。
class Record(object): pass def calculate_pnl(records, node_prices): for record in records: try: # fill RT and DA prices from the hash retrieved above if hasattr(record, 'sink') and record.sink: record.da = node_prices[record.sink][0] - node_prices[record.id][0] record.rt = node_prices[record.sink][1] - node_prices[record.id][1] else: record.da = node_prices[record.id][0] record.rt = node_prices[record.id][1] # calculate dependent values: RT-DA and PNL record.rtda = record.rt - record.da record.pnl = record.rtda * record.mw except: print sys.exc_info() def map_rows(cursor, mappings, callback=None): records = [] for row in cursor: record = Record() for field, attr in mappings.iteritems(): setattr(record, attr, getattr(row, field, None)) if not callback or callback(record): records.append(record) return records def get_positions(cursor): # get the latest position time cursor.execute("SELECT latest data time") time = cursor.fetchone().time hour = eelib.util.get_hour_ending(time) # fetch the current positions cursor.execute("SELECT stuff FROM atable", (hour)) # read the rows nodes = {} def record_callback(record): if abs(record.mw) > 0: if record.id: nodes[record.id] = None return True else: return False records = util.map_rows(cursor, { 'id': 'id', 'name': 'name', 'mw': 'mw' }, record_callback) # query prices for node_id in nodes: # RT price row = cursor.execute("SELECT price WHERE ? ? ?", (node_id, time, time)).fetchone() rt5 = row.lmp if row else None # DA price row = cursor.execute("SELECT price WHERE ? ? ?", (node_id, hour, hour)).fetchone() da = row.da_lmp if row else None # update the hash value nodes[node_id] = (da, rt5) # calculate the position pricing calculate_pnl(records, nodes) # sort records.sort(key=lambda r: r.name) # return the records return records
解决方案
我们是否考虑过使用ORM? SQLAlchemy非常好,Elixir使它漂亮。它确实可以减少处理数据库所需的样板代码量。而且,已经提到了许多陷阱,SQLAlchemy开发人员已经处理了这些陷阱。
由于性能问题,在iPhone应用程序上使用ORM可能不是一个好主意,我们希望代码尽可能快。因此,我们无法避免样板代码。如果我们正在考虑使用ORM,除了SQLAlchemy之外,我还建议我们使用Storm。
根据我们要对数据进行多少操作,可能不需要填充中间对象。游标的标题数据结构将使我们获得列名,进行一些自省,使我们可以为行创建一个具有col-name:value对的字典。
我们可以将字典传递给%运算符。 odbc模块的文档将说明如何获取列元数据。
此代码段以这种方式显示%运算符的应用。
>>> a={'col1': 'foo', 'col2': 'bar', 'col3': 'wibble'} >>> 'Col1=%(col1)s, Col2=%(col2)s, Col3=%(col3)s' % a 'Col1=foo, Col2=bar, Col3=wibble' >>>
空的Record类和(通常)应用于单个Record的自由浮动函数表明我们没有正确设计类。
class Record( object ): """Assuming rtda and pnl must exist.""" def __init__( self ): self.da= 0 self.rt= 0 self.rtda= 0 # or whatever self.pnl= None # self.sink = None # Not clear what this is def setPnl( self, node_prices ): # fill RT and DA prices from the hash retrieved above # calculate dependent values: RT-DA and PNL
现在,calculate_pnl(records,node_prices)
更简单并且可以正确使用该对象。
def calculate_pnl( records, node_prices ): for record in records: record.setPnl( node_prices )
关键不是要以小方式琐碎地重构代码。
重点是:一个类封装了责任。
是的,空洞的课程通常是一个问题。这意味着职责分散在其他地方。
对记录的收集也有类似的分析。这不仅仅是一个简单的列表,因为集合(作为一个整体)具有它执行的操作。
" Request-Transform-Render"不太正确。我们有一个模型(Record类)。构建Model的实例(可能是由于请求)。Model对象负责其自身的状态转换和更新。也许它们由检查其状态的某个对象显示(或者渲染)。
正是"转换"步骤经常通过分散责任来破坏良好的设计。 "转换"是对非对象设计的保留,在该设计中,责任是一个模糊的概念。