在 Python 中,如何确保在离开代码块之前数据库连接始终关闭?我们希望尽可能防止数据库连接保持打开状态,因为此代码将在密集使用服务器上运行,此处的人员已经告诉我们数据库连接应尽快关闭。
具体示例如下:
def do_something_that_needs_database():
dbConnection = MySQLdb.connect(host=args['database_host'], user=args['database_user'], passwd=args['database_pass'], db=args['database_tabl'], cursorclass=MySQLdb.cursors.DictCursor)
dbCursor = dbConnection.cursor()
dbCursor.execute('SELECT COUNT(*) total FROM table')
row = dbCursor.fetchone()
if row['total'] == 0:
print('error: table have no records')
dbCursor.execute('UPDATE table SET field="%s"', whatever_value)
return None
print('table is ok')
dbCursor.execute('UPDATE table SET field="%s"', another_value)
# a lot more of workflow done here
dbConnection.close()
# even more stuff would come below
我们认为,当表中没有行时,该方法将使数据库连接保持打开状态,虽然我们仍不确定它是如何工作的。无论如何,从某种意义上说,这可能是一种糟糕的设计,因为可以在每个小执行块之后打开和关闭 DB 连接。当然,在这种情况下,我们可以在 return 之前添加一个 close。
2、解决方案
解决方案 1:使用 try/finally 语句
传统的方法是使用 try/finally 语句,如下所示:
def do_something_that_needs_database():
dbConnection = MySQLdb.connect(host=args['database_host'], user=args['database_user'], passwd=args['database_pass'], db=args['database_tabl'], cursorclass=MySQLdb.cursors.DictCursor)
try:
# as much work as you want, including return, raising exceptions, _whatever_
finally:
closeDb(dbConnection)
解决方案 2:使用 with 语句
从 Python 2.6(以及 2.5 带有 from future import with_statement)开始,还有一种替代方法(尽管 try/finally 仍然非常有效!):with 语句。
with somecontext as whatever:
# the work goes here
上下文具有一个 enter 方法,在进入时执行(如果需要,返回上述 whatever),还有一个 exit 方法,在退出时执行。尽管这种方法很优雅,但由于没有现成的上下文以您想要的方式工作,因此构建一个所需的工作(尽管在 2.6 中通过 contextlib 减少了)可能应该表明传统的 try/finally 最好。
如果您有 2.6 并想尝试 contextlib,则可以通过以下方式“隐藏”try/finally,如下所示:
import contextlib
@contextlib.contextmanager
def dbconnect(**kwds):
dbConnection = MySQLdb.connect(**kwds)
try:
yield dbConnection
finally:
closeDb(dbConnection)
可用于如下所示:
def do_something_that_needs_database():
with dbconnect(host=args['database_host'], user=args['database_user'],
passwd=args['database_pass'], db=args['database_tabl'],
cursorclass=MySQLdb.cursors.DictCursor) as dbConnection:
# as much work as you want, including return, raising exceptions, _whatever_
如果要多次使用它,这可能值得这样做,只是为了避免针对这些多次使用反复重复 try/finally。
解决方案 3:使用 try/finally 块
def do_something_that_needs_database():
dbConnection = MySQLdb.connect(host=args['database_host'], user=args['database_user'], passwd=args['database_pass'], db=args['database_tabl'], cursorclass=MySQLdb.cursors.DictCursor)
try:
# Do some processing
finally:
# Cleanup
解决方案 4:使用 contextlib 方法
假设您使用的 DB 驱动程序开箱即用不支持,请尝试使用 contextlib 中的 closing 方法。