Подтвердить что ты не робот

Почему диспетчер контекста MySQLdb не закрывает курсор?

MySQLdb Connections имеет рудиментарный менеджер контекста, который создает курсор для ввода, либо откатывается, либо совершает на выходе, и неявно не подавляет исключения. Из Источник подключения:

def __enter__(self):
    if self.get_autocommit():
        self.query("BEGIN")
    return self.cursor()

def __exit__(self, exc, value, tb):
    if exc:
        self.rollback()
    else:
        self.commit()

Итак, кто-нибудь знает, почему курсор не закрыт при выходе?


Сначала я предположил, что это было потому, что закрытие курсора ничего не делало, и что курсоры только имели близкий метод в знак уважения к API-интерфейс Python DB (см. комментарии к этому ответу). Однако факт заключается в том, что закрытие курсора сжигает оставшиеся наборы результатов, если таковые имеются, и отключает курсор. Из источника курсора:

def close(self):
    """Close the cursor. No further queries will be possible."""
    if not self.connection: return
    while self.nextset(): pass
    self.connection = None

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

def __del__(self):
    self.close()
    self.errorhandler = None
    self._result = None

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

Чтобы быть предельно ясным, этот пример, очевидно, не имеет смысла:

with conn as cursor:
    cursor.execute(select_stmt)

rows = cursor.fetchall()

Это должно быть:

with conn as cursor:
    cursor.execute(select_stmt)
    rows = cursor.fetchall()

И этот пример не имеет смысла:

# first transaction
with conn as cursor:
    cursor.execute(update_stmt_1)

# second transaction, reusing cursor
try:
    cursor.execute(update_stmt_2)
except:
    conn.rollback()
else:
    conn.commit()

Это должно быть просто:

# first transaction
with conn as cursor:
    cursor.execute(update_stmt_1)

# second transaction, new cursor
with conn as cursor:
    cursor.execute(update_stmt_2)

Опять же, что может быть вредным для закрытия курсора при выходе, и какие преимущества там не для его закрытия?

4b9b3361

Ответ 1

Чтобы ответить на ваш вопрос напрямую: я не вижу никакого вреда в закрытии в конце блока with. Я не могу сказать, почему это не сделано в этом случае. Но, поскольку в этом вопросе нет недостатка в работе, у меня был поиск в истории кода и вы могли бы высказать несколько соображений (догадки) о том, почему close() может не будет вызываться:

  • Существует небольшая вероятность того, что вращение через вызовы nextset() может вызвать исключение - возможно, это было замечено и рассматривается как нежелательное. Возможно, поэтому более новая версия cursors.py содержит эту структуру в close():

    def close(self):
        """Close the cursor. No further queries will be possible."""
        if not self.connection:
            return
    
        self._flush()
        try:
            while self.nextset():
                pass
        except:
            pass
        self.connection = None
    
  • Существует (несколько удаленный) потенциал, который может занять некоторое время, чтобы прокрутить все оставшиеся результаты, ничего не делая. Поэтому close() не может быть вызван, чтобы избежать ненужных итераций. Думаете ли вы, что стоит экономить эти тактовые циклы, я считаю субъективным, но вы можете спорить по строкам "если это не нужно, не делайте этого".

  • При выполнении функции sourceforge функциональность была добавлена ​​в trunk с помощью этого коммита в 2007 году и, похоже, этот раздел connections.py не изменился поскольку. Это объединение на основе этого коммита, которое имеет сообщение

    Добавьте поддержку Python-2.5 с инструкцией, описанной в http://docs.python.org/whatsnew/pep-343.html. Пожалуйста, проверьте

    И код, который вы цитируете, никогда не изменялся с тех пор.

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


Более современная версия

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

Обратите внимание, что в более поздней версии этого модуля реализованы __enter__() и __exit__() внутри cursor: см. здесь. __exit__() здесь делает вызов self.close(), и, возможно, это обеспечивает более стандартный способ использования синтаксиса с синтаксисом, например.

with conn.cursor() as c:
    #Do your thing with the cursor

Заключительные примечания

N.B. Думаю, я должен добавить, насколько я понимаю сбор мусора (не эксперт), как только нет ссылок на conn, он будет освобожден. В этот момент не будет ссылок на объект курсора, и он также будет освобожден.

Однако вызов cursor.close() не означает, что он будет собирать мусор. Он просто сжигает результаты и устанавливает соединение с None. Это означает, что он не может быть повторно использован, но он не будет собирать мусор немедленно. Вы можете убедиться в этом, вручную вызвав cursor.close() после вашего блока with, а затем, скажем, напечатав некоторый атрибут cursor


N.B. 2 Я думаю, что это несколько необычное использование синтаксиса with, поскольку объект conn сохраняется, поскольку он уже находится во внешней области - в отличие, скажем, от более распространенного with open('filename') as f:, где нет объектов зависания со ссылками после окончания блока with.