LINUX.ORG.RU

Python, общее пространство имен для ипортируемых модулей

 ,


0

3

Привет!

У меня питоновый проект с кучей модулей. Модули, по сути, не зависят друг от друга - могут импортироваться как угодно или вообще работать в режиме "скрипта". В целом вся работа внутри этих модулей крутится вокруг базы данных. Так вот, когда в каком-то скрипте я хочу использовать много этих модулей то сталкиваюсь с проблемой в виде долгой инициализации всего этого дела - каждый модуль коннектится к базе, вытаскивает всю нужную ему структуру таблиц и так далее. Все бы ничего, только пул коннектов к базе можно было бы и один использовать, а не свой для каждого модуля, таблицы между модулями тоже "пересекаются" и тянуть всю их метадату несколько раз мне совсем не хочется, ну и прочее. В итоге я придумал объявлять все это добро в модулях вот так:

if 'foo' in dir(sys.modules['__main__']):
    foo = sys.modules['__main__'].foo
else:
    foo = 'bar'
    sys.modules['__main__'].foo = foo

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

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

Спасибо за советы!

Сделать отдельный модуль для коннекта к базе?

Deleted
()
Ответ на: комментарий от aedeph_

Еще раз - все модули независимы и могут работать как угодно, в том числе и по одиночке. То есть мне нужно вызывать отдельный модуль с пулами и таблицами в каждом модуле, и я опять получу то что у меня и так есть. Или же мне надо чтоб модули могли как-то узнавать друг о друге, в чем и вопрос.

То же самое, alexferman

micronekodesu ★★★
() автор топика
Последнее исправление: micronekodesu (всего исправлений: 1)
Ответ на: комментарий от micronekodesu

У тебя там по одной функции на каждый модуль? Сделай один-единственный модуль для работы с базой.

Deleted
()

Отдельный модуль, представляющий собой прослойку между остальной «кучей модулей» и реальной БД. Вся «куча» делает коннекты к БД через эту прослойку. Прослойка уже сама разруливает запросы к ней, решает, когда настоящее новое соединение создавать, а когда воспользоваться уже имеющимся.

Примерно как работают кэширующие сервера YouTube.

Crocodoom ★★★★★
()
Последнее исправление: Crocodoom (всего исправлений: 1)
Ответ на: комментарий от Deleted

У тебя там по одной функции на каждый модуль?

Конечно нет.

Сделай один-единственный модуль для работы с базой.

Он будет огроменный (прикинул сейчас по модулям для одного логического куска - примерно 2K LOC) и перегруженный - разделение на модули выполнялось еще и с тем соображением что иногда достаточно одного\двух из них, а иногда для какой-то большой задачи приходится тащить чуть ли не все.

micronekodesu ★★★
() автор топика
Ответ на: комментарий от Crocodoom

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

micronekodesu ★★★
() автор топика
Ответ на: комментарий от micronekodesu

Я не понимаю, как код из твоего стартового поста решает проблему.

Модули [...] могут [...] вообще работать в режиме «скрипта».

Это означает, что в top висит два различных процесса python3]. Тогда как тут поможет пляска вокруг dir(sys.modules['__main__']) ?

Crocodoom ★★★★★
()
Ответ на: комментарий от Deleted

Импортируй только нужные методы.

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

micronekodesu ★★★
() автор топика
Ответ на: комментарий от micronekodesu

Ты загрузку таблиц в __init__ засунул что ли?

Deleted
()
Ответ на: комментарий от Crocodoom

Я не понимаю, как код из твоего стартового поста решает проблему.

Модуль - m1.py

#!/usr/bin/env python3

import sys

if 'foo' in dir(sys.modules['__main__']):
    foo = sys.modules['__main__'].foo
else:
    foo = 'm1'
    sys.modules['__main__'].foo = foo

if 'bar' in dir(sys.modules['__main__']):
    bar = sys.modules['__main__'].bar
else:
    bar = 'm1'
    sys.modules['__main__'].bar = bar


def get():
    print('m1: ', foo, bar)


if __name__ == '__main__':
    get()

Работает сам:

$ ./m1.py 
m1:  m1 m1

Импортируется:

>>> foo = 'bar'
>>> import m1
>>> m1.get()
m1:  bar m1

Копия модуля m1 - m2.py, абсолютно все то же самое, только поменял значения переменных с "m1" на "m2". Думаю показывать как он работает сам по себе нет смысла, так что импорты:

>>> foo = 'bar'
>>> import m1
>>> m1.get()
m1:  bar m1
>>> import m2
>>> m2.get()
m2:  bar m1
micronekodesu ★★★
() автор топика
Ответ на: комментарий от Deleted
$ cat importmodule.py 
#!/usr/bin/env python3

print('init foo')
foo = 'foo'
print('init bar')
bar = 'bar'

def fprint():
    print(foo)

def bprint():
    print(bar)

$ python3
>>> from importmodule import fprint
init foo
init bar
>>> fprint()
foo
micronekodesu ★★★
() автор топика
Ответ на: комментарий от micronekodesu

Копия модуля m1 - m2.py, абсолютно все то же самое, только поменял значения переменных с «m1» на «m2»

Ты наркоман что ли? Зачем для этого отдельный модуль? Да даже отдельная функция?

Deleted
()
Ответ на: комментарий от Deleted

Сам ты наркоман, это примеры, я просто показываю что я имею ввиду. Нафига мне тут выкладывать "реальные" модули, если для описания проблемы достаточно простых примеров такого плана?!

micronekodesu ★★★
() автор топика
Ответ на: комментарий от micronekodesu

Я ещё раз вопрос повторю. У тебя два модуля могут работать одновременно «в режиме скрипта» или нет? Если могут, то никаким dir(sys.modules['__main__']) ты доступ из одного скрипта к другому не получишь, и поэтому коннект к базе не расшаришь.

Crocodoom ★★★★★
()
Ответ на: комментарий от micronekodesu

Ладно, не сердись)

Понятно, что у тебя при импорте модуля выполняются процедуры вне дефоф. Убери их оттуда.

Deleted
()
Ответ на: комментарий от Deleted

Зачем? Представь что с "foo" у меня работает сто функций, мне в каждой нужно будет его определять (точнее, дергать функцию, которая определит)? А если учесть что у меня там два десятка таких переменных, представь сколько мне придется в каждом def'е дергать дополнительных функций. И, в любом случае, это опять таки каждый раз будет переопределение заново - у меня чтоб на с одной таблицей поработать приходится тянуть еще десяток других, на которые она завязана, и еще сотню всяких там констрейтов и прочего.

micronekodesu ★★★
() автор топика
Ответ на: комментарий от micronekodesu

Представь что с «foo» у меня работает сто функций, мне в каждой нужно будет его определять (точнее, дергать функцию, которая определит)?

Да. А что тебя смущает?

Deleted
()
Ответ на: комментарий от Crocodoom

У тебя два модуля могут работать одновременно «в режиме скрипта» или нет?

Могут.

Если могут, то никаким dir(sys.modules['__main__']) ты доступ из одного скрипта к другому не получишь, и поэтому коннект к базе не расшаришь.

А мне и не надо. Если они запущены отдельно то пусть сами себе все это инициализируют. Если они используются как импортируемые модули - пусть "делятся".

micronekodesu ★★★
() автор топика
Ответ на: комментарий от Deleted

А если учесть что у меня там два десятка таких переменных, представь сколько мне придется в каждом def'е дергать дополнительных функций. И, в любом случае, это опять таки каждый раз будет переопределение заново - у меня чтоб на с одной таблицей поработать приходится тянуть еще десяток других, на которые она завязана, и еще сотню всяких там констрейтов и прочего.

micronekodesu ★★★
() автор топика
Ответ на: комментарий от Deleted

Не понял. У меня есть объекты - таблицы в бд, когда я с ними работаю то конечно к ним обращаюсь. В одной функции у меня выборка из таблицы, в другой, например, delete записей во второй таблице, в третьей функции - insert в во вторую таблицу данных из первой, которые тянуться связями из записей в третьей таблице по условию, ну и так далее. Так что да, мне приходится обращаться к этим объектам. То что переменные в примере это строки - только для примера и простоты - по сути мне надо обеспечить "общий доступ" к объектам - таблицам в базе.

micronekodesu ★★★
() автор топика
if 'foo' in dir(sys.modules['__main__']):
    foo = sys.modules['__main__'].foo else:
    foo = 'bar'
    sys.modules['__main__'].foo = foo
if 'foo' in globals():
    foo = foo
else:
    foo = 'bar'

Не запускал, но, по-моему, эквивалентно.

Virtuos86 ★★★★★
()
Последнее исправление: Virtuos86 (всего исправлений: 1)
Ответ на: комментарий от Virtuos86

'foo' in globals()

Нет, так не будет работать, потому что он смотрит в globals импортируемого модуля:

$ cat globalscheck.py 
#!/usr/bin/env python3

print('foo' in globals())

$ python3
>>> foo = 'bar'
>>> import globalscheck
False
>>> 'foo' in globals()
True

Ну и даже если определить в импортируемом модуле второй модуль этого не увидит.

Первый код вообще не понял. Если это попытка сделать в одну строку то тогда

foo = sys.modules['__main__'].foo if 'foo' in dir(sys.modules['__main__']) else 'bar'

sys.modules['__main__'].foo = foo
micronekodesu ★★★
() автор топика
Последнее исправление: micronekodesu (всего исправлений: 1)
Ответ на: комментарий от micronekodesu

Хорошо. Я хотел убедиться, что у этих модулей всегда будет общий райнтайм.

Теперь разбираем твою проблему.

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

На это ты ответил, что

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

Это означает, что ты не понимаешь, как работает импорт модулей в Python. В пределах одного рантайма модуль загружается только один раз. Внутренний код выполняется только один раз, и неважно, сколько раз написана строчка import module. Это легко проверить с помощью команды print.

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

Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

Это означает, что ты не понимаешь, как работает импорт модулей в Python.

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

"Основной" модуль, отвечает за коннекты, m0.py:

import uuid

dbdata = {}


def getTable(table):
    if table not in dbdata:
        dbdata[table] = uuid.uuid4().hex
    return dbdata[table]

Пример модулей, который работает с базой - m1.py, m2.py, код одинаковый, но чтоб импортнуть два раза это отдельные файлы:

import m0
foo = m0.getTable('foo')
bar = m0.getTable('bar')

Проверяю:

$ cat script.py
#!/usr/bin/env python3

import m1
import m2

print(m1.foo, m2.foo, m1.foo == m2.foo)
print(m1.bar, m2.bar, m1.bar == m2.bar)

$ ./script.py 
071c8ebb0a60413a8b6636dd3c2570f9 071c8ebb0a60413a8b6636dd3c2570f9 True
746cc3f675ab440aa3b476f10eb58bfb 746cc3f675ab440aa3b476f10eb58bfb True
micronekodesu ★★★
() автор топика
Последнее исправление: micronekodesu (всего исправлений: 2)
Ответ на: комментарий от micronekodesu

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

micronekodesu ★★★
() автор топика

Напомни, почему ты не сделаешь в модуле ф-цию run() в которую передашь коннектор к БД ? Внутри модулей можешь проверять на __main__ и там создавать свой коннектор, если так хочется, хотя я бы не вызывал их отдельно, а просто набор вызываемых скриптов передавал параметром в оркестратор. Можешь вообще в класс все это запилить, в __init__ или self.run() передавай коннектор, если не передал (is None) создавай новый. А у тебя какой-то велосипед из гибких костылей.

Dred ★★★★★
()
Ответ на: комментарий от micronekodesu

Можно сделать синглтон-коннектор и для каждого модуля отдельный файл-прослойку для работы с БД который этот коннектор использует.

Почему нет?

KillTheCat ★★★★★
()
Ответ на: комментарий от Dred

в модуле ф-цию run() в которую передашь коннектор к БД

если не передал (is None) создавай новый

Мне кроме коннектора еще нужно кучу таблиц передавать. И все это можно, конечно, завернуть в функцию в модуле, но тогда я в каждом модуле буду заново эти таблицы прогружать. Это все и так есть, и меня не устраивает.

Ну или я тебя не так понял, можешь показать на примере?

micronekodesu ★★★
() автор топика
Ответ на: комментарий от KillTheCat

Так вообще и есть - у меня есть модуль с функциями для работы с базой, которые на вход получают запрос, который нужно выполнить, и объект подключения к БД. И когда я в каждом модуле использую свое соединение и заново выгружаю все таблицы - это получается долго. Я выше уже писал, что в таком случае у меня инициализация пачки модулей в рамках одного скрипта занимает в 4 раза больше времени, чем когда я при инициализации таблиц\коннектов проверяю делал ли я это уже и отдаю существующий объект.

micronekodesu ★★★
() автор топика
Ответ на: комментарий от micronekodesu

И когда я в каждом модуле использую свое соединение и заново выгружаю все таблицы

А почему свое соединение если коннектор уже есть? ЯННП.

KillTheCat ★★★★★
()
Ответ на: комментарий от KillTheCat

Так, я тоже не понял - у меня есть два варианта, либо я создаю соединение в модуле работы с БД при инициализации (или наподобие того, как сделано в «работающем» варианте несколькими сообщениями выше) и использую его, ли я в каждом модуле создаю свое соединение и передаю его в функции для работы с базой.

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

micronekodesu ★★★
() автор топика
Последнее исправление: micronekodesu (всего исправлений: 1)
Ответ на: комментарий от micronekodesu

Первый код вообще не понял.

Криво процитировал просто, else, конечно, на другой строке должен стоять.

Я бы посмотрел в сторону написания своей функции импорта, емнип, надо смотреть модуль importlib.

Virtuos86 ★★★★★
()
Ответ на: комментарий от micronekodesu

Да, всё хорошо.

Если хочешь избавиться от глобальных переменных типа dbdata, то делай простейший синглтон

class X:                                                                        
    instance = None                                                             
    def __new__(cls):                                                           
        if not cls.instance:                                                    
            cls.instance = super(X, cls).__new__(cls)                           
        return cls.instance                                                     

Все экземпляры такого класса (то есть x = X()) будут одним и тем же объектом, что позволяет шарить данные между модулями через атрибуты этого объекта.

Crocodoom ★★★★★
()
Ответ на: комментарий от micronekodesu

кроме коннектора еще нужно кучу таблиц передавать

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

Dred ★★★★★
()
Ответ на: комментарий от Crocodoom

Спасибо огромное!

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

micronekodesu ★★★
() автор топика
Ответ на: комментарий от micronekodesu

Удачи!

Напоследок советую тщательно погуглить, возможно есть уже готовые пакеты, предоставляющие такой интерфейс — прослойку между клиентским кодом и БД, оптимизированную под шаринг. Возможно даже, что для перехода на такую прослойку тебе почти не придётся ничего менять в уже готовых модулях. Смотря как сделан интерфейс. В общем изучи стек.

Crocodoom ★★★★★
()
Ответ на: комментарий от micronekodesu

Я просто сократил 48 слов до 4 слов. Если открыл, то не понятно зачем все эти хороводы с dbdata и getTable, чем не устраивает sqlalchemy.MetaData?

Использовать дополнительный синглтон, выступающий прокси-объектом, без необходимости - спорное решение :). Если хочется сделать правильно, то нужен общий метод инициализации для всех скриптов, который бы устанавливал то, как скрипт должен работать с БД. Но вообще в твоем случае действительно достаточно синглтона, который в питоне не нужно реализовывать способом Crocodoom'а. Просто создай инстанс sqla.MetaData в отдельном модуле, и используй его.

ei-grad ★★★★★
()
Ответ на: комментарий от ei-grad

не понятно зачем все эти хороводы с dbdata и getTable

Ты понимаешь вообще какая задача решается?

общий метод инициализации для всех скриптов, который бы устанавливал то, как скрипт должен работать с БД

Судя по всему не понимаешь.

Просто создай инстанс sqla.MetaData в отдельном модуле, и используй его.

Я и так использую инстанс для метадаты. Вообще интересно было бы послушать как использовать объекты таблицы, и при этом не использовать метаданные (ну кроме варианта с rawsql конечно).

micronekodesu ★★★
() автор топика
Ответ на: комментарий от ei-grad

Ну да. Тогда мне не совсем понятно к чему эти советы про то чтоб использовать sqlalchemy и metadata. Я не показывал это в примерах, потому что в целом вопрос был не про работу с БД, а про то как использовать один объект между разными модулями, и для этого тех "функций", на примере которых задача обсуджалась в треде, вполне достаточно.

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

micronekodesu ★★★
() автор топика
Ответ на: комментарий от micronekodesu

Просто с большой вероятностью твое dbdata это MetaData.tables, можно не плодить сущности, все нужные методы у тебя уже есть. Если ты не собираешься использовать ORM да ещё с разными реализациями - то дополнительная прослойка не нужна.

общий метод инициализации для всех скриптов, который бы устанавливал то, как скрипт должен работать с БД

Суть в том, чтобы явно перенести логику подготовки БД в отдельный кусок кода, например, класс типа такого:

class CommandManager:

    def __init__(self):
        self.tables = set()
        self.scripts = []

    def register_script(self, tables_for_script, script):
        self.scripts.append(script)
        self.tables.add(tables_for_script)

    def run(self):
        for table in self.tables:
            self.load_metadata(table)
        for script in self.scripts:
            self.run_script(script)

В методе run_script - реализовать запуск скриптов с передачей им необходимых параметров.

Метод register_script вызывать, и создавать сам CommandManager, можно по-разному. Например сделать синглтон (просто глобальную переменную в модуле, модули в Python - сами по себе синглтоны) и обернуть все скрипты декораторами с указанием списков таблиц. Или в какой-нибудь функции main явно вызвать register_script по определенному там же списку скриптов.

Но, повторюсь, это может быть оверкилл, и всё что тебе не хватало, вероятно, это:

from . import db
if 'table1' not in db.metadata.tables:
    db.load_table('table1')

С оглядкой на многопоточность можно обернуть load_table блокировками по ключу. И if тоже можно перенести внутрь load_table.

ei-grad ★★★★★
()
Последнее исправление: ei-grad (всего исправлений: 3)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.