В 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
Я считаю, что соединение базы данных открывается, когда на столе нет строки, а Я по-прежнему не уверен, как это работает.
Во всяком случае, может быть, это плохой дизайн в том смысле, что я мог открыть и закрыть соединение с БД после каждого небольшого блока execute
. И конечно, я мог бы просто добавить close
прямо перед return
в этом случае...
Но как я мог бы всегда правильно закрывать БД, не беспокоясь, если у меня есть это return
, или raise
, или continue
, или что-нибудь среднее? Я думаю что-то вроде блока кода, похожее на использование try
, как в следующем предложении, которое, очевидно, не работает:
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:
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)
# again, that same lot of line codes done here
except ExitingCodeBlock:
closeDb(dbConnection)
# still, that "even more stuff" from before would come below
Я не думаю, что для исключения есть что-то похожее на ExitingCodeBlock
, я знаю, что есть try else
, но я надеюсь, что Python уже имеет подобную функцию...
Или, может быть, кто-то может предложить мне движение парадигмы и сказать мне, что это ужасно и очень советую мне никогда этого не делать. Может быть, это просто не о чем беспокоиться, и пусть MySQLdb обрабатывает его или не так ли?
Ответы
Ответ 1
Традиционный подход - это оператор 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)
Так как Python 2.6 (и 2.5 с a from __future__ import with_statement
), есть альтернатива (хотя try
/finally
работает отлично!): инструкция with
.
with somecontext as whatever:
# the work goes here
Контекст имеет метод __enter__
, выполняемый при записи (для возврата whatever
выше, если хотите) и метода __exit__
, выполняемого при выходе. Несмотря на элегантность, поскольку нет существующего контекста, который работает так, как вы хотите, работа, необходимая для создания одного (хотя и уменьшена в 2.6 с помощью contextlib
), вероятно, должна предполагать, что хорошая старая попытка/наконец-то лучше.
Если у вас есть 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 снова и снова для каждого из этих видов использования.
Ответ 2
Если MySQLdb поддерживает его, то вы можете использовать оператор . Для этой причины существует оператор "с". Однако для этого требуется, чтобы объект определял __enter__ и __exit__ для работы.
В качестве примера инструкции for... для чтения/записи файлов вы можете:
with open('filename','r') as file:
for line in file:
# processing....
# File automatically closed afterwards or if there was an exception thrown
Если он не поддерживает его, то вы всегда можете использовать try... наконец, как в:
try:
# Do some processing
finally:
# Cleanup
Предложение finally выполняется независимо от того, как завершается попытка (завершилась ли успешно, или исключение было распространено, но поймано, или было создано исключение и будет продолжать распространяться).
Ответ 3
Предполагая, что используемый драйвер DB не поддерживает with
, попробуйте метод closing
из contextlib
.
Ответ 4
Почему бы просто не обернуть его при попытке: finally: block?
http://docs.python.org/tutorial/errors.html#defining-clean-up-actions
Это то, что, наконец, для блоков.