Структура данных памятки в БД
Какова лучшая структура данных для кэширования (сохранения / сохранения / запоминания), поэтому многие функции приводят к базе данных. Предположим, функция calc_regress с текущим определением в python:
def calc_regress(ind_key, dep_key, count=30):
independent_list = sql_select_recent_values(count, ind_key)
dependant_list = sql_select_recent_values(count, dep_key)
import scipy.stats as st
return st.linregress(independent_list, dependant_list)
Я вижу ответы на вопрос : какую структуру таблицы следует использовать для хранения параметров и результатов запомненных функций в реляционной базе данных? но кажется, что это решает проблему только одной функции, в то время как у меня есть около 500 функций.
2 ответа
Вариант А
Вы можете использовать структуру в связанном ответе, ненормализованную с количеством столбцов = максимальное количество аргументов среди 500 функций. Также необходимо добавить столбец для имени функции.
Тогда вы могли бы сделать SELECT * FROM expensive_func_results WHERE func_name = 'calc_regress' AND arg1 = ind_key AND arg2 = dep_key and arg3 = count
, так далее.
Конечно, это не очень хороший дизайн для использования. Для той же функции, вызываемой с меньшим количеством параметров, столбцы с нулевыми значениями / несоответствиями должны игнорироваться; в противном случае вы получите несколько строк результатов.
Вариант Б
Создайте таблицу / структуру как func_name
, arguments
, result
где 'arguments' - это всегда словарь kwargs или позиционные аргументы, но не смешанный для каждой записи. Даже при том, что kwargs dict хранится в виде строки, порядок ключей-> значений в нем не является предсказуемым / непротиворечивым, даже если это одни и те же аргументы. Поэтому вам нужно будет заказать его перед преобразованием в строку и ее сохранением. Когда вы хотите запросить, вы будете использовать SELECT * FROM expensive_func_results WHERE func_name = 'calc_regress' AND arguments = 'str(kwargs_dict)'
, где str(kwargs_dict)
это то, что вы установите программно. Это также может быть установлено на результат inspect.getargspec
, (или же inspect.getcallargs
) хотя вам придется проверять последовательность.
Вы не сможете выполнять запросы на комбинации аргументов, если вы не предоставите все аргументы для запроса или частичное совпадение с LIKE
,
Вариант С
Нормализовано полностью: одна таблица func_calls
как func_name
, args_combo_id
, arg_name_idx
, arg_value
, В каждой строке таблицы будет храниться один аргумент для одной комбинации из аргументов вызова этой функции. Еще один стол func_results
как func_name
, args_combo_id
, result
, Вы также можете нормализовать дальше func_name
быть сопоставленным с func_id
,
В этом порядке порядок аргументов ключевых слов не имеет значения, так как вы будете выполнять внутреннее объединение для выбора каждого параметра. Этот запрос должен быть построен программно или выполнен с помощью хранимой процедуры, поскольку количество соединений, необходимых для извлечения всех параметров, определяется количеством параметров. Ваша функция выше имеет 3 параметра, но у вас может быть другой с 10. arg_name_idx
это "имя аргумента или индекс", поэтому он также работает для смешанных kwargs + args. Некоторое дублирование может произойти в таких случаях, как calc_regress(ind_key=1, dep_key=2, count=30)
а также calc_regress(1, 2, 30)
(так же как calc_regress(1, 2)
со значением по умолчанию для count <- этого случая следует избегать, запись таблицы должна иметь все аргументы); так как args_combo_id
будет отличаться для обоих, но результат, очевидно, будет одинаковым. Опять же, модуль проверки может помочь в этой области.
[Править] PS: Дополнительно для func_name
Вам может понадобиться использовать полное имя, чтобы избежать конфликтов между модулями в вашем пакете. И декораторы могут мешать этому; без deco.__name__ = func.__name__
, так далее.
PPS: Если объекты передаются функциям, которые запоминаются в БД, убедитесь, что их __str__
это что-то полезное и повторяемое / последовательное для хранения в качестве значений arg.
Этот конкретный случай не требует повторного создания объектов из значений arg в БД, в противном случае вам нужно будет сделать __str__
или же __repr__
как способ __repr__
предполагалось (но обычно не делается):
это должно выглядеть как допустимое выражение Python, которое можно использовать для воссоздания объекта с тем же значением (при условии соответствующей среды).
Я бы использовал здесь хранилище значений ключей, где ключ может быть конкатенацией id
объекта функции (чтобы гарантировать уникальность ключа) и его аргументы, в то время как значение будет возвращаемым значением функции.
Так calc_regress(1, 5, 30)
вызов даст пример ключа 139694472779248_1_5_30
где первая часть id(calc_regress)
, Пример функции создания ключа:
>>> def produce_cache_key(fun, *args, **kwargs):
... args_key = '_'.join(str(a) for a in args)
... kwargs_key = '_'.join('%s%s' % (k, v) for k, v in kwargs.items())
... return '%s_%s_%s' % (id(fun), args_key, kwargs_key)
Вы можете сохранить свои результаты в памяти, используя словарь и декоратор:
>>> def cache_result(cache):
... def decorator(fun):
... def wrapper(*args, **kwargs):
... key = produce_cache_key(fun, *args, **kwargs)
... if key not in cache:
... cache[key] = fun(*args, **kwargs)
... return cache[key]
... return wrapper
... return decorator
...
>>>
>>> @cache_result(cache_dict)
... def fx(x, y, z=0):
... print 'Doing some expensive job...'
...
>>> cache = {}
>>> fx(1, 2, z=1)
Doing some expensive job...
>>> fx(1, 2, z=1)
>>>