Sum together list of F expressions

Question:

Is there a way to specify (in an annotation or aggregation) that a sequence of F expressions should be summed together without manually typing out F("first_prop") + F("second_prop") + ...?

I want something similar to how python’s sum() function allows you to pass an iterable and get the sum of the values in the iterable i.e. sum([1,2,3]) returns 6.

Concretely, I want something that looks like this:

class Tree(TimeStampedModel):
  leaf_count = models.IntegerField()
  branch_count = models.IntegerField()

Tree.objects.create(leaf_count=60, branch_count=8)
Tree.objects.create(leaf_count=30, branch_count=3)

# now I want to annotate a combined count using my imaginary IterableSum aggregator
combined_sums = list(
  Tree.objects.all().annotate(
    combined_count=IterableSum(fields=[F("leaf_count"), F("branch_count")])
  ).values_list("combined_count", flat=True)
)

combined_sums # [68, 33]

How can I achieve this?

Asked By: Uche Ozoemena

||

Answers:

The only problem with sum is that it starts with 0 as initial value. You can you can use reduce from functools:

from functools import reduce
from operator import add

from django.db.models import F

combined_sums = list(
    Tree.objects.values(
        combined_count=reduce(add, [F('leaf_count'), F('branch_count')]),
        flat=True,
    )
)

although strictly speaking, that is not even necessary, you can just use sum, since it will add up 0 with F('leaf_count'):

from django.db.models import F

combined_sums = list(
    Tree.objects.values(
        combined_count=sum([F('leaf_count'), F('branch_count')]),
        flat=True,
    )
)

Then this will have a + 0 in the query, which might not be ideal.

This is because sum does not sum only integers, indeed, you can for example sum F objects:

In [11]: sum([F('foo'), F('bar')])
Out[11]: <CombinedExpression: Value(0) + F(foo) + F(bar)>
Answered By: Willem Van Onsem