Как работать с соединениями с базой данных в модуле библиотеки Python

23

Я создал библиотеку в Python, которая содержит функции для доступа к базе данных. Это библиотека-обертка вокруг базы данных сторонних приложений, написанная из-за того, что стороннее приложение не предлагает достойного API. Теперь я изначально позволял каждой функции открывать соединение с базой данных на время вызова функции, что было в порядке, пока моя программная логика не использовала вложенные вызовы функций, где я бы затем вызывал определенную функцию несколько тысяч раз. Это было не очень эффективно. Профилирование показало, что накладные расходы были при настройке соединения с базой данных - один раз на вызов функции. Поэтому я переместил открытое соединение из функции (ей) в сам модуль, чтобы соединение с базой данных было открыто при импорте библиотечного модуля. Это дало мне приемлемую производительность.

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

leancz
источник
1
Предоставьте openConnфункцию и сделайте так, чтобы пользователь передавал ее каждой вызываемой функции таким образом, чтобы они могли охватывать соединение в withвыражении или чем-то еще
Даниэль Гратцер
1
Я согласен с jozfeg, рассмотрите возможность создания класса, который открывает соединение db внутри конструктора и закрывает соединение при выходе
Ник Бернс

Ответы:

31

Это действительно зависит от используемой вами библиотеки. Некоторые из них могут закрывать соединение самостоятельно (Примечание: я проверил встроенную библиотеку sqlite3, но это не так). Python будет вызывать деструктор, когда объект выходит из области видимости, и эти библиотеки могут реализовывать деструктор, который изящно закрывает соединения.

Однако это может быть не так! Я бы порекомендовал, как и другие в комментариях, обернуть его в объект.

class MyDB(object):

    def __init__(self):
        self._db_connection = db_module.connect('host', 'user', 'password', 'db')
        self._db_cur = self._db_connection.cursor()

    def query(self, query, params):
        return self._db_cur.execute(query, params)

    def __del__(self):
        self._db_connection.close()

Это создаст экземпляр вашего подключения к базе данных в начале и закроет его, когда место, где был создан ваш объект, выйдет из области видимости. Примечание. Если вы создадите экземпляр этого объекта на уровне модуля, он сохранится для всего вашего приложения. Если это не предусмотрено, я бы предложил отделить функции базы данных от функций, не связанных с базой данных.

К счастью, python стандартизировал API базы данных , поэтому он будет работать со всеми совместимыми БД для вас :)

Travis
источник
Как избежать , что selfв def query(self,?
Самайо
2
определить избежать? Self - это то, что определяет это как метод экземпляра, а не метод класса. Я предполагаю, что вы можете создать базу данных как статическое свойство класса, а затем использовать только методы класса (нигде в себе не нужны), но тогда база данных будет глобальной для класса, а не только для его индивидуальной реализации.
Трэвис
Да, потому что я пытался использовать твой пример, чтобы сделать простой запрос, db.query('SELECT ...', var)и он жаловался на необходимость третьего аргумента.
Самайо
@ samson, сначала нужно создать экземпляр MyDBобъекта:db = MyDB(); db.query('select...', var)
Cowbert
Это мешало сообщениямResourceWarning: unclosed <socket.socket...
Боб Стейн
3

при обработке соединений с базой данных необходимо учитывать две вещи:

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

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

Чтобы закрепить все эти понятия, посмотрите следующий пример, который включает в себя psycopg2

import psycopg2


class Postgres(object):
"""docstring for Postgres"""
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            # normally the db_credenials would be fetched from a config file or the enviroment
            # meaning shouldn't be hardcoded as follow
            db_config = {'dbname': 'demo', 'host': 'localhost',
                     'password': 'postgres', 'port': 5432, 'user': 'postgres'}
            try:
                print('connecting to PostgreSQL database...')
                connection = Postgres._instance.connection = psycopg2.connect(**db_config)
                cursor = Postgres._instance.cursor = connection.cursor()
                cursor.execute('SELECT VERSION()')
                db_version = cursor.fetchone()

            except Exception as error:
                print('Error: connection not established {}'.format(error))
                Postgres._instance = None

            else:
                print('connection established\n{}'.format(db_version[0]))

        return cls._instance

    def __init__(self):
        self.connection = self._instance.connection
        self.cursor = self._instance.cursor

    def query(self, query):
        try:
            result = self.cursor.execute(query)
        except Exception as error:
            print('error execting query "{}", error: {}'.format(query, error))
            return None
        else:
            return result

    def __del__(self):
        self.connection.close()
        self.cursor.close()
ponach
источник
1
Здравствуй! Спасибо за ваш ответ. Но когда я пытаюсь реализовать это в моем случае, у меня есть if Database._instance is None: NameError: name 'Database' is not defined. Я не могу понять, что Databaseи как я мог это исправить.
Илья Русин
1
@IlyaRusin, это была моя вина, на самом деле Database - это просто родительский класс, в который я помещаю общие методы для обработки различных RDBMS, так как я подключаюсь не только к Postgres. Однако, извините за ошибку, и я надеюсь, что исправленная версия может быть полезна для вас, не стесняйтесь добавлять, изменять код в соответствии с вашими потребностями, если у вас есть какие-либо вопросы, не стесняйтесь
Понах
Если бы я неоднократно звонить Postgres.query(Postgres(), some_sql_query)в whileцикле, будет ли еще открыть и закрыть соединение в каждой итерации, или держать его открытым в течение всего времени whileцикла до выхода из программы?
@ Майкл, класс соединения реализован как одноэлементный, поэтому он будет создаваться только один раз, но в целом я бы рекомендовал не использовать предложенный способ вызова, а инициировать его в переменной
ponach
1
@ponach Спасибо, это именно то, чего я хотел достичь. Я немного адаптировал ваш код и попытался использовать оператор UPDATE в вашей query()функции, но, кажется, есть проблема с моим кодом, когда я запускаю свое приложение «параллельно». Я сделал отдельный вопрос об этом: softwareengineering.stackexchange.com/questions/399582/…
2

Было бы интересно предложить возможности менеджера контекста для ваших объектов. Это означает, что вы можете написать такой код:

class MyClass:
    def __init__(self):
       # connect to DB
    def __enter__(self):
       return self
    def __exit__(self):
       # close the connection

Это предложит вам удобный способ автоматически закрыть соединение с базой данных, вызвав класс с помощью оператора with:

with MyClass() as my_class:
   # do what you need
# at this point, the connection is safely closed.
Биллаль Бегерадж
источник
-1

Долго думать об этом. Сегодня я нашел способ. я не знаю, что это лучший способ. вы создаете файл с именем: conn.py и сохраняете его в /usr/local/lib/python3.5/site-packages/conn/ папке. Я использую freebsd, и это путь к моей папке site-packages. в моем conn.py: conn = "имя_бдома = всеядный пользователь = пароль postgres = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` и в сценарии я хочу вызвать соединение, я пишу:

импорт psycopg2 импорт psycopg2.extras импорт psycopg2.extensions

из conn import conn try: conn = psycopg2.connect (conn.conn) за исключением: page = "Нет доступа к базе данных"

cur = conn.cursor ()

и бла-бла ....

я надеюсь, что это полезно

Фонг
источник