数据库访问生成器函数有什么优势?

5
有没有比在while循环中调用.fetchone()更好的写法?如果有,它的优点是什么?谢谢。
def testf2():
    db = connectToMysqlDB(None)

    sql_statement = " ".join([
        "select d.* ",
        "from dr_snapshot d ",
        "order by d.PremiseID asc, d.last_read asc; "])

    sel_cur = db.cursor()
    rc = sel_cur.execute(sql_statement)

    loop_ok = True
    while loop_ok:
        meter_row = sel_cur.fetchone()
        if meter_row:
            yield meter_row
        else:
            loop_ok = False

    yield None

for read_val in testf2():
   print(read_val)
   #Perform something useful other than print.

('610159000', 6, datetime.datetime(2012, 7, 25, 23, 0), 431900L, 80598726L)
('610160000', 6, datetime.datetime(2012, 7, 25, 23, 0), 101200L, 80581200L)
None

可能只是因为缩短了语句,更容易说出 for read in testf2():doSomething,而不是整个语句... 如果它只在一个地方使用,那么你并没有得到太多好处,但如果你在代码中反复执行该查询,则将其放入函数中符合 DRY 的原则。 - Joran Beasley
Python对于MySQL的包是否不遵循sqlite3模块的先例,将游标实现为可迭代对象本身,从而允许您执行for meter_row in sel_cur:操作? - JAB
@JAB 谢谢。这是一个很好的观点。 - octopusgrabbus
3个回答

5

不用。从功能上来说两者是一样的,但是如果你想重复使用这段代码,包装成生成器会有一些优势。例如,你可以在生成器块中添加关闭连接/光标的代码,以确保它们在读取完成后被关闭。我建议你把这段代码加入到你的代码中,这样光标就能够被正确关闭了。

def testf2():
    try:
        db = connectToMysqlDB(None)

        sql_statement = " ".join([
            "select d.* ",
            "from dr_snapshot d ",
            "order by d.PremiseID asc, d.last_read asc; "])

        sel_cur = db.cursor()
        rc = sel_cur.execute(sql_statement)

        loop_ok = True
        while loop_ok:
            meter_row = sel_cur.fetchone()
            if meter_row:
                yield meter_row
            else:
                loop_ok = False
    except ProgrammingError:
        print "Tried to read a cursor after it was already closed"
    finally:
        sel_cur.close()

这样做将使代码更易于重用,因为您只需要在一个地方正确处理连接管理。


当生成器对象在用尽之前被垃圾回收(例如,循环体中引发异常)时会发生什么?将光标包装在实现 __del __() 的可迭代对象中是否更好? - André Caron
@AndréCaron http://docs.python.org/release/2.5/whatsnew/pep-342.html "添加close()方法有一个不明显的副作用。当生成器被垃圾回收时,会调用close()方法,这意味着生成器的代码在被销毁之前有最后一次运行的机会。这最后的机会意味着生成器中的try...finally语句现在可以得到保证;finally子句现在总是有机会执行。" 不需要__del__()方法。 - JAB
@JAB:你说的是生成器的close()方法,可以在客户端调用。如果生成器像这样使用for item in generator,那么.close()方法不会被调用(至少我在迭代器类型的文档中找不到任何支持该说法的声明)。我从未见过关闭可迭代对象(或生成器)的代码。我知道对象仍将被收集并最终调用__del __(),但最好确保及时完成。 - André Caron
1
看起来在垃圾回收后确实会调用close()。不过它的工作方式很有趣,它是一个隐式函数,会引发错误。请参见http://www.python.org/dev/peps/pep-0342/以获取示例。我已更新我的答案,以考虑关闭游标连接。 - Wulfram
@AndréCaron 您的建议是在一个类中完成吗?您能给出一个例子吗? - octopusgrabbus
显示剩余2条评论

4

看起来我是对的,mySQL游标是可迭代的(https://dev59.com/OHI-5IYBdhLWcg3wiY3a#1808414)。因此,你可以像这样而不是使用while循环(但我喜欢将访问数据库的代码放在生成器函数中的想法,所以请保留):

for meter_row in sel_cur:
    yield meter_row

请注意,最后的yield None可能是不必要的;StopIteration异常用于指示迭代器输出的耗尽,并且是for循环用作停止循环的标志,因此如果包括yield None,则最终输出中会出现None,但没有实际收益。请保留HTML标记。

0
使用生成器可以使您在现有代码中更灵活地使用结果。例如,您可以直接将其传递给一个 csv.writerwriterows 函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接