在Python中操作数据库结果集的最佳实践?

6 投票
4 回答
3985 浏览
提问于 2025-04-11 09:21

我正在写一个简单的Python网页应用,这个应用有好几页的商业数据,专门为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

4 个回答

0

根据你想对数据做多少处理,你可能不需要填充一个中间对象。光标的头部数据结构可以让你获取列名,通过一些简单的检查,你可以为每一行创建一个包含列名和对应值的字典。你可以把这个字典传递给%运算符。关于如何获取列的元数据,odbc模块的文档会有详细说明。

下面这段代码展示了如何使用%运算符。

>>> a={'col1': 'foo', 'col2': 'bar', 'col3': 'wibble'}
>>> 'Col1=%(col1)s, Col2=%(col2)s, Col3=%(col3)s' % a
'Col1=foo, Col2=bar, Col3=wibble'
>>> 
1

你有没有考虑过使用ORM?SQLAlchemy挺不错的,而Elixir让它看起来更美观。使用这些工具可以大大减少你在处理数据库时需要写的重复代码。此外,很多提到的那些“小坑”问题,SQLAlchemy的开发者们已经处理过了。

2

空的记录类和那个可以单独使用的函数,通常说明你的类设计得不太好。

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 )

这里的重点不是简单地小改代码。

关键在于: 一个类应该承担特定的责任

没错,看起来空空的类 确实 通常是个问题。这意味着它的责任分散在其他地方。

对于记录的集合也是如此。这不仅仅是一个简单的列表,因为这个集合整体上有它自己要执行的操作。

所谓的“请求-转换-渲染”其实不太准确。你有一个模型(记录类)。模型的实例会被创建(可能是因为某个请求)。这些模型对象负责自己的状态变化和更新。也许它们会被某个检查它们状态的对象显示出来(或渲染)。

正是这个“转换”步骤,常常会导致设计不佳,因为它把责任分散到各个地方。“转换”这个概念源于非面向对象的设计,那时责任是个模糊的概念。

撰写回答