QuerySet class eq dunder

I noticed behaviour which, on face value, doesn’t seem pythonic. Say I want to check if all products have the same category:

b.products.filter(category=b.products.first().category) == b.products.all()

So they’re not. Except they are:

b.products.filter(category=b.products.first().category).count() == b.products.all().count()

To be clear, the int comparison is only a logical check that they are in fact the same - I know it’s different programmatically.

I suspect this behaviour is unpythonic and not particularly useful to the coder. When you compare lists, sets and dicts they compare the contents, not instances. What use is the way you’re doing it?

Probably there’s a very good reason why your eq dunder compares instances but right now I don’t see it.

1 Like

Actually, this is extremely “Pythonic”, since this is a Python operation (==) being performed. (There is no __eq__ method defined on the QuerySet class, at least not that I can find.)

What is returned from b.products.all() or b.products.filter(..) is not a list, dict, set, or tuple. It’s an object of type QuerySet. And, in python, two objects are compared by comparing their object IDs and not by their attributes.

In the example you’ve provided here, it’s unlikely that either queryset in that first equation were even evaluated, so no results were even retrieved from the database. (I just check it out in a test system. Two queries testing for equivalence did not cause either query to be resolved.)

If you need to check the contents in an expression like this, then you would want to force the queries to be resolved and return “comparable objects” before trying to compare them.

1 Like

Also, even if we made this work as OP expected, it’s terrible for performance. A much faster approach is to get the database to do the calculation:

all_same_category = b.products.aggregate(c=Count("category", distinct=True)) == 1

This is only worth it if the products aren’t to be loaded in the current request. If they are, a list comprehension is better:

products = b.products.all()
all_same_category = len({p.category_id for p in products}) == 1
1 Like