В 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.