Yax
Yax

Reputation: 2189

How to combine Python's `operator.and_` and `operator.or_` in Django query?

I don't know if this is obtainable but how do I combine Python's operator.and_ and operator.or_ in this Django query?

I want to get articles that have @trending or #trending and not in loaded_list from Articles model. Let's assume loaded_list = [3,4,2,5,7,8,3]

One way I did this was:

from django.db.models import Q
articles = Articles.objects.filter(Q(content__icontains = '@trending') | Q(content__icontains = '#trending') & ~Q(id__in = loaded_list))[:5]

The code below is just to show what I want to achieve but how do I write it correctly?

import operator
from django.db.models import Q

query = reduce(operator.and_|or_,[(['@trending' or '#trending']), ~Q(id__in = loaded_list )])
articles = Articles.objects.filter(query)

Upvotes: 1

Views: 6070

Answers (1)

Peter DeGlopper
Peter DeGlopper

Reputation: 37319

I'm still not sure why you'd want to do this the way you're describing, but it is certainly possible to write the expression you gave with operator.or_ and operator.and_. Doing so will give you no benefits over using | and & to express the same logic, but it's legal.

operator.or_ and operator.and_ are functional wrappers for the same operation the | and & operators perform. Generally you use them in cases where having the operation available as a callable is useful, such as calling reduce over a sequence that's not known at the time you're writing the code. For example, this is a concise (but utterly un-optimized, please don't actually use it) factorial function for positive integers:

import operator
def slow_factorial(n):
    return reduce(operator.mul, xrange(1, n+1))

One clause of your query is:

Q(content__icontains = '@trending') | Q(content__icontains = '#trending')

That can also be written as:

operator.or_(Q(content__icontains = '@trending'),
             Q(content__icontains = '#trending'))

You can achieve the same ending Q object (probably less efficiently, but I haven't actually profiled) with:

some_q = reduce(operator.or_,
                (Q(content__icontains = '@trending'),
                 Q(content__icontains = '#trending')))

That Q object can then be combined with another:

articles = Articles.objects.filter(some_q & ~Q(id__in = loaded_list))

Which is identical to:

articles = Articles.objects.filter(operator.and_(some_q, ~Q(id__in = loaded_list)))

Or, again as a less readable expression:

query = reduce(operator.and_,
               (some_q,
                ~Q(id__in=loaded_list)))
articles = Articles.objects.filter(query)

Or being totally explicit:

query = reduce(operator.and_,
               (reduce(operator.or_,
                       (Q(content__icontains = '@trending'),
                        Q(content__icontains = '#trending'))),
                ~Q(id__in=loaded_list))

I think I've gotten the parens right there, but I wouldn't be shocked to learn that I've balanced them wrong - the ease of making that kind of mistake is just one reason this is an inferior implementation compared to the simple expression you used in your question.

I hope to be able to amend this answer if you can explain more about what you're trying to do and why it's desirable to use reduce and the operator names rather than the built in operator syntax. Is the list of terms you want to search for unknown at coding time and also of unknown length? That's the first case I can think of where reduce etc would help.

Upvotes: 8

Related Questions