Можно ли выполнять код при импорте модуля? - PullRequest
11 голосов
/ 28 февраля 2012

Я разрабатываю небольшое приложение с графическим интерфейсом для упаковки sqlite DB (простые операции CRUD). Я создал три модели sqlalchemy (m_person, m_card.py, m_loan.py, все в папке /models) и ранее в каждой из них был следующий код:

from sqlalchemy import Table, Column, create_engine
from sqlalchemy import Integer, ForeignKey, String, Unicode
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref, relation

engine = create_engine("sqlite:///devdata.db", echo=True)
declarative_base = declarative_base(engine)
metadata = declarative_base.metadata

Это было немного неправильно (СУХОЙ), поэтому было предложено переместить все эти вещи на уровень модуля (в models/__init__.py).

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import Table, Column, Boolean, Unicode
from settings import setup

engine = create_engine('sqlite:///' + setup.get_db_path(), echo=False)
declarative_base = declarative_base(engine)
metadata = declarative_base.metadata

session = sessionmaker()
session = session()

.. и импортировать declarative_base примерно так:

from sqlalchemy import Table, Column, Unicode
from models import declarative_base


class Person(declarative_base):
    """
    Person model

    """
    __tablename__ = "people"

    id = Column(Unicode(50), primary_key=True)
    fname = Column(Unicode(50))
    sname = Column(Unicode(50))

Однако я получил много отзывов о том, что выполнение кода при импорте модуля, как это плохо? Я ищу окончательный ответ о том, как правильно сделать это, как кажется, пытаясь удалить повторение кода, я ввел некоторые другие плохие практики. Любая обратная связь была бы очень полезна. ( Ниже приведен метод get_db_path() из settings/setup.py для полноты, как он вызывается в приведенном выше коде models/__init__.py. )

def get_db_path():

    import sys
    from os import makedirs
    from os.path import join, dirname, exists
    from constants import DB_FILE, DB_FOLDER

    if len(sys.argv) > 1:
        db_path = sys.argv[1]
    else:
        #Check if application is running from exe or .py and adjust db path accordingly
        if getattr(sys, 'frozen', False):
            application_path = dirname(sys.executable)
            db_path = join(application_path, DB_FOLDER, DB_FILE)
        elif __file__:
            application_path = dirname(__file__)
            db_path = join(application_path, '..', DB_FOLDER, DB_FILE)

    #Check db path exists and create it if not
    def ensure_directory(db_path):
        dir = dirname(db_path)
        if not exists(dir):
            makedirs(dir)

    ensure_directory(db_path)

    return db_path

Ответы [ 4 ]

5 голосов
/ 29 февраля 2012

Некоторые популярные фреймворки (например, Twisted) выполняют много логики инициализации во время импорта. Преимущества возможности динамического создания содержимого модуля имеют свою цену, одна из них заключается в том, что IDE не всегда могут решить, что находится в модуле или нет.

В вашем конкретном случае вы можете захотеть провести рефакторинг, чтобы конкретный движок поставлялся не во время импорта, а позже. Вы можете создать метаданные и ваш класс declarative_base во время импорта. Затем во время запуска, после того, как все классы определены, вы вызываете create_engine и связываете результат с вашим sqlalchemy.orm.sessionmaker. Но если ваши потребности просты, вам даже не нужно заходить так далеко.

В общем, я бы сказал, что это не Java или C. Нет причин бояться делать что-то на уровне модуля, кроме определения функций, классов и констант. Ваши классы создаются, когда приложение все равно запускается, один за другим. Небольшое исправление классов (в том же модуле!) Или создание одной или двух глобальных таблиц поиска - это нормально, на мой взгляд , если это упрощает вашу реализацию .

Чего бы я определенно избегал, так это любого кода в вашем модуле, который приводит к тому, что порядок импорта имеет значение для ваших пользователей (кроме обычного способа простого предоставления логики), или который изменяет поведение кода вне вашего модуля. , Тогда ваш модуль становится черной магией, которая принята в мире Perl (ala use strict;), но я считаю, что это не "pythonic".

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

3 голосов
/ 29 февраля 2012

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

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

Посмотрите документацию для Declarative_base и обратите внимание, как примеры могут создать модель с помощью declarative_base предоставленного класса, который еще не связан с ядром базы данных.В идеале вы хотите сделать это, а затем иметь какую-то функцию или класс соединения, которые будут управлять созданием сеанса и затем связывать его с базой.

2 голосов
/ 29 февраля 2012

Нет ничего плохого в выполнении кода во время импорта.

Указание выполняется достаточно для того, чтобы ваш модуль можно было использовать, но не настолько, чтобы импортировать его было неоправданно медленно, и не так много, как вы.неоправданно ограничивать то, что можно сделать с ним.

Как правило, это означает определение классов, функций и глобальных имен (на самом деле это уровень модуля - бит неправильного числа), а также импорт чего-либо ваших классов, функций и т. д.., необходимо работать.

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

2 голосов
/ 29 февраля 2012

Я тоже наткнулся на это и создал файл database.py с классом менеджера баз данных, после чего я создал один глобальный объект.Таким образом, класс мог читать настройки из моего файла settings.py для настройки базы данных, и первый класс, которому нужно было использовать базовый объект (или сеанс / механизм), инициализировал глобальный объект, после чего каждый просто использовал его повторно.Я чувствую себя намного более комфортно, имея «из DM импорта myproject.database» в верхней части каждого класса, используя SQLAlchemy ORM, где DM - это мой глобальный объект базы данных, а затем DM.getBase (), чтобы получить базовый объект.

Вот мой класс database.py:

Session = scoped_session(sessionmaker(autoflush=True))

class DatabaseManager():
    """
    The top level database manager used by all the SQLAlchemy classes to fetch their session / declarative base.
    """
    engine = None
    base = None

    def ready(self):
        """Determines if the SQLAlchemy engine and base have been set, and if not, initializes them."""
        host='<database connection details>'
        if self.engine and self.base:
            return True
        else:
            try:
                self.engine = create_engine(host, pool_recycle=3600)
                self.base = declarative_base(bind=self.engine)
                return True
            except:
                return False

    def getSession(self):
        """Returns the active SQLAlchemy session."""
        if self.ready():
            session = Session()
            session.configure(bind=self.engine)
            return session
        else:
            return None

    def getBase(self):
        """Returns the active SQLAlchemy base."""
        if self.ready():
            return self.base
        else:
            return None

    def getEngine(self):
        """Returns the active SQLAlchemy engine."""
        if self.ready():
            return self.engine
        else:
            return None

DM = DatabaseManager()
...