qarthandso qarthandso - 2 months ago 17
Python Question

Understanding Django Q - Dynamic

I'm reading this article on dynamically generating Q objects. I understand (for the most part) Q objects but I'm not understanding how the author specifically is doing this example:

# string representation of our queries
>>> predicates = [('question__contains', 'dinner'), ('question__contains', 'meal')]

# create the list of Q objects and run the queries as above..
>>> q_list = [Q(x) for x in predicates]

>>> Poll.objects.filter(reduce(operator.or_, q_list))
[<Poll: what shall I make for dinner>, <Poll: what is your favourite meal?>]


What I specifically don't get is the list comprehension. A
Q
object is formatted with arbitrary keywords arguments as such
Q(question__contains='dinner')
.

If doing it like the author suggests with the list comprehension, won't that effectively just place a tuple inside a
Q
object on each iteration? Like such:
Q(('question__contains', 'dinner'))
.

I'm not sure how this code produces a correctly formatted
Q
object.

Answer

The article relies on the undocumented feature that Q() accepts args as well as kwargs.

If you look at source code for the Q class, you can see that it does the following in the __init__ method.

class Q(tree.Node):
    ...
    def __init__(self, *args, **kwargs):
        super(Q, self).__init__(children=list(args) + list(kwargs.items()))

If you call Q(question__contains=dinner) then args in an empty tuple () and kwargs is a dictionary {'question__contains': 'dinner'}. In the super() call, the children variable is

children = list(args) + list(kwargs.items())

which evaluates to

children = list(()) + list(('question__contains', 'dinner'),)

which simplifies to

children = [('question__contains', 'dinner')]

Note that you could also get this result if you use Q(('question__contains', 'dinner')). In this case, args is a tuple (('question__contains', 'dinner'),) and kwargs is an empty dictionary {}.

In the super() call, the children variable evaluates to

children = list((('question__contains', 'dinner'),)) + list([])

which simplifies to the same result as before,

children = [('question__contains', 'dinner')]

We have shown that Q(question__contains=dinner) is equivalent to Q(('question__contains', 'dinner')), and therefore you can generate the list of Q() objects by looping over a list of 2-tuples in a list comprehension.

>>> predicates = [('question__contains', 'dinner'), ('question__contains', 'meal')]

# create the list of Q objects and run the queries as above..
>>> q_list = [Q(x) for x in predicates]
Comments