Реализация логического парсера в django-запросе - PullRequest
3 голосов
/ 05 января 2010

Это будет "длинный". Я включаю как можно больше кода и объяснений ... Я не стесняюсь выбрасывать код, если это необходимо.

Я пытаюсь реализовать логический парсер в системе запросов django. Где пользователи могут предоставлять сложные запросы к тегам, которые применяются к образцам. По сути, это часть хранилища научных образцов, где пользователи могут применять определенные теги (тип ткани, изученное заболевание и т. Д.). Затем они могут создавать постоянные «корзины» выборок, определенных логическим запросом к этим тегам.

#models.py

class Sample(models.Model):
    name = models.CharField(max_length = 255)


class Tag(models.Model):
    name = models.CharField(max_length = 255)
    samples = models.ManyToManyField(Sample)

A quick example:
#example data:
Sample1 has TagA, TagB, TagC
Sample2 has       TagB, TagC, TagD
Sample3 has TagA,       TagC, TagD
Sample4 has       TagB

#example query:
'TagB AND TagC AND NOT TagD'

вернул бы Sample1. Я использую сумасшедший взлом строки для создания набора Q() объектов:

def MakeQObject(expression):
    """
    Takes an expression and uses a crazy string-eval hack to make the qobjects.
    """
    log_set = {'AND':'&','OR':'|','NOT':'~'}

    exp_f = []
    parts = expression.split()
    #if there is a ) or ( then we can't use this shortcut
    if '(' in parts or ')' in parts:
        return None

    for exp in parts:
        if exp in log_set:
            exp_f.append(log_set[exp])
        else:
            exp_f.append("Q(tags__name__iexact = '%s')" % exp)
    st = ' '.join(exp_f)
    qobj = eval(st)
    return qobj

Однако, это не работает для всего, что требует сложного порядка операций или группировки по (). Для тех же данных примера запрос: (TagA OR TagB) AND NOT TagD должен возвращать Sample1, Sample4, но не возвращает. Я реализовал функцию «по одному за раз», которая может взять один объект Sample и выполнить запрос. Тем не менее, в моей фактической базе данных у меня есть ~ 40000 сэмплов и ~ 400 тэгов (примерно ~ 7 на сэмпл), и итеративный метод занимает ~ 4 минуты для завершения на всех сэмплах. Поэтому я подсчитываю корзины по ночам, а затем просто замораживаю их в течение дня. Я беспокоюсь о том, что когда я начну выкапывать больше корзин, образцов и меток, это будет плохо масштабироваться.

Есть предложения?

1 Ответ

1 голос
/ 05 января 2010

Во-первых, для повышения производительности , вероятно, поможет добавить индекс в поле имени тега, поскольку вы используете его для запросов. Итак, добавьте db_index = True в свой столбец:

class Tag(models.Model):
    name = models.CharField(max_length = 255, db_index=True)
    samples = models.ManyToManyField(Sample)

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

Если это слишком много для вас, попробуйте свернуть свое собственное руководство Фредрика Простой анализ сверху вниз в Python .

...