В Python, как убедиться, что соединение с базой данных всегда будет закрываться перед выходом из блока кода? - PullRequest
16 голосов
/ 15 мая 2010

Я хочу, чтобы соединение с базой данных было как можно более открытым, потому что этот код будет работать на интенсивно используемом сервере, и люди уже сказали мне, что соединения с базами данных всегда должны быть закрыты как можно скорее.

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 справится с этим, или это так?

Ответы [ 4 ]

23 голосов
/ 15 мая 2010

Традиционный подход - это выражение 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 с 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, это один из способов, которым вы могли бы сделать это, чтобы "спрятать" попытку / наконец ...:

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_

Может быть, это стоит того, если вы собираетесь использовать это много-много раз, просто чтобы не повторять попытку / окончание снова и снова для каждого из этих многочисленных применений.

6 голосов
/ 15 мая 2010

Если MySQLdb поддерживает это, то вы можете использовать оператор " с ". Утверждение «с» существует только по этой причине. Однако для этого необходимо, чтобы объект определил __enter__ и __exit__.

В качестве примера оператора with ... для чтения / записи файлов вы можете получить:

with open('filename','r') as file:
    for line in file:
        # processing....
# File automatically closed afterwards or if there was an exception thrown

Если это не поддерживается, тогда вы всегда можете использовать try ... finally, как в:

try:
    # Do some processing
finally:
    # Cleanup

Предложение finally выполняется независимо от того, как завершается попытка (было ли оно выполнено успешно, исключение было передано, но перехвачено, или исключение было сгенерировано и будет продолжать распространяться).

4 голосов
/ 15 мая 2010

Предполагая, что используемый драйвер БД не поддерживает with из коробки, попробуйте метод closing из contextlib.

3 голосов
/ 15 мая 2010

Почему бы просто не обернуть это в попытку: finally: блок?

http://docs.python.org/tutorial/errors.html#defining-clean-up-actions

Это то, для чего в конечном итоге блоки.

...