Як боротися з підключеннями до бази даних в модулі бібліотеки Python


23

Я створив бібліотеку в Python, яка містить функції доступу до бази даних. Це бібліотека обгортки навколо бази даних сторонніх додатків, написана через те, що стороннє додаток не пропонує гідного API. Тепер я спочатку дозволяв кожній функції відкривати підключення до бази даних протягом тривалості виклику функції, що було в порядку, поки моя логіка програми не використовувала вкладені виклики до функцій, де я б потім викликав певну функцію кілька тисяч разів. Це було не дуже добре. Профілювання цього показало, що накладні витрати були в налаштуваннях підключення до бази даних - один раз на виклик функції. Тож я перенесла відкрите з'єднання зсередини функції (функцій) до самого модуля, щоб з'єднання бази даних було відкрито при імпорті модуля бібліотеки. Це дало мені прийнятну ефективність.

Зараз у мене є два питання щодо цього. По-перше, чи потрібно мене турбувати, що я більше не явно закриваю підключення до бази даних і як я можу це зробити явно за допомогою цієї настройки? По-друге, чи те, що я зробив, потрапляє де-небудь близько до сфери належної практики і як я інакше можу підійти до цього?


1
Надайте openConnфункцію та змусьте користувача передати її до кожної функції, яку вона викликає, таким чином вони зможуть охопити з'єднання у withвиписці чи будь- якому іншому
Daniel Gratzer

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 баз даних , тому це працюватиме з усіма сумісними БД для вас :)


Як уникнути , що selfв def query(self,?
samayo

2
визначити уникати? Self - це те, що визначає це як метод екземпляра, а не метод класу. Я думаю, ви могли б створити базу даних як статичну властивість класу, а потім використовувати лише методи класу (не потрібні ніде), але тоді база даних буде глобальною для класу, а не лише окремою інстанцією.
Тревіс

Так, тому що я спробував використати ваш приклад, щоб зробити простий запит, db.query('SELECT ...', var)і він скаржився на необхідність третього аргументу.
самайо

@samson, спочатку потрібно створити MyDBоб'єкт:db = MyDB(); db.query('select...', var)
cowbert

Це запобігло повідомленнямResourceWarning: unclosed <socket.socket...
Боб Штейн

3

під час роботи з підключеннями до бази даних слід хвилювати дві речі:

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

  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()

1
Привіт! Спасибі за вашу відповідь. Але коли я намагаюся реалізувати це у своєму випадку, у мене є if Database._instance is None: NameError: name 'Database' is not defined. Я не можу зрозуміти, що Databaseтаке і як я можу це виправити.
Ілля Русін

1
@IlyaRusin, що я був винним, насправді База даних - це лише батьківський клас, в якому я вкладаю загальні методи для різної обробки RDBMS, оскільки я підключаюся не лише до Postgres. Однак вибачте за помилку, і я сподіваюся, що виправлена ​​версія може бути корисною для вас, не соромтесь додати, змінити код під ваші потреби, якщо у вас виникне будь-яке пов'язане з цим питання.
ponach

Якби я повторно дзвонив Postgres.query(Postgres(), some_sql_query)у whileцикл, чи все-таки він би відкривав і закривав з'єднання в кожній ітерації, чи залишав би його відкритим протягом усього часу whileциклу, поки програма не завершиться?

@Michael клас з'єднання реалізований як синглтон, отже, він буде створений лише один раз, але в цілому я б рекомендував проти запропонованого способу виклику, замість цього ініціювати його у змінній
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, і це шлях до папки мого сайту-пакунків. в моєму conn.py: conn = "dbname = всенадійний користувач = postgres пароль = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` ` і в сценарії я хочу зателефонувати на з'єднання, я пишу:

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

from conn import conn try: conn = psycopg2.connect (conn.conn) за винятком: page = "Неможливо отримати доступ до бази даних"

cur = conn.cursor ()

і бла-бла….

сподіваюся, це корисно

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.