运行Python生成器清理代码的最佳方法

5
我正在尝试编写一个生成器函数,从数据库中获取行并逐个返回它们。然而,我不确定下面标有**的清理代码是否按照我想象的那样执行。如果没有执行,将清理代码放在生成器内部并在最后一个yield语句之后执行的最佳方法是什么?我查看了捕获StopIteration,但似乎是从调用者而不是从生成器中完成的。
def MYSQLSelectGenerator(stmt):
...
try:   
    myDB = MySQLdb.connect(host=..., port=..., user=..., passwd=..., db=...)   
    dbc=myDB.cursor()
    dbc.execute(stmt)
    d = "asdf"
    while d is not None:
        d = dbc.fetchone() #can also use fetchmany() to be more efficient
        yield d
    dbc.close() #** DOES THIS WORK AS I INTEND, MEANING AS SOON AS d = "None"
except MySQLdb.Error, msg:
    print("MYSQL ERROR!")
    print msg

虽然您没有经验,但您可能想添加一个finally子句并将清理放在其中 - 看起来这就是它的预期用途。try语句 - wwii
3个回答

5

你的版本会在d为None时立即运行dbc.close(),但如果出现异常,则不会运行。你需要一个finally子句。这个版本保证会运行dbc.close(),即使出现异常:

try:   
    myDB = MySQLdb.connect(host=..., port=..., user=..., passwd=..., db=...)   
    dbc = myDB.cursor()
    dbc.execute(stmt)
    d = "asdf"
    while d is not None:
        d = dbc.fetchone() #can also use fetchmany() to be more efficient
        yield d
except MySQLdb.Error, msg:
    print("MYSQL ERROR!")
    print msg
finally:
    dbc.close()

有没有办法将这两个结合起来?这种方法的问题在于,dbc.close()不再位于捕获mysql错误的try块中,就像在我的版本中一样。答案是在finally子句中放置另一个try块吗?那很丑陋但是... - Tommy
良好的API设计规定,像close这样的方法不应该抛出异常(除非连接已经关闭等情况),因为客户端将处于一种状态,你不确定连接是否被清理干净。您应该与相关库的作者确认此事,但我认为可以安全地假设该行不会有任何异常。 - Benjamin Hodgson
finally中的代码无论出现错误与否都将被执行。 - Zachary Vance

5

你可以使用 finally 代码块来实现类似的功能。另外,还有一个选项(虽然在这里可能有点过头了,但它是一个非常有用的技巧)就是创建一个支持 with 语句的类:

class DatabaseConnection:
    def __init__(self, statement):
        self.statemet = statement
    def __enter__(self): 
        self.myDB = MySQLdb.connect(host=..., port=...,user=...,passwd=...,db=...)
        self.dbc = myDB.cursor()
        self.dbc.execute(self.statement)
        self.d = "asdf"
    def __exit__(self, exc_type, exc_value, traceback):
        self.dbc.close()

    def __iter__(self):
        while self.d is not None:
            self.d = self.dbc.fetchone()
            yield self.d


with DatabaseConnection(stmnt) as dbconnection:
    for i in dbconnection:
        print(i)

3
您可以使用上下文管理器和with语句。 contextlib提供了closing
from contextlib import closing

myDB = MySQLdb.connect(host=..., port=..., user=..., passwd=..., db=...)   
with closing(myDB.cursor()) as dbc:
    dbc.execute(stmt)
    d = "asdf"
    while d is not None:
        d = dbc.fetchone() #can also use fetchmany() to be more efficient
        yield d

这将在with块结束时自动调用dbcclose()方法,即使在执行过程中出现异常也会执行。


我之前已经正式接受了Ben的答案,但是这个答案更正确,因为上下文管理器已经处理了这种复杂性。 - Tommy

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