I think adding a "first" method to the QuerySet be useful.

164 views
Skip to first unread message

Maxime Haineault

unread,
Jul 3, 2012, 12:27:34 AM7/3/12
to django-d...@googlegroups.com
One of the common pitfall I come across way to often with novices is something like this:

    def getFirstUser():
        return User.objects.all()[0]

It looks innocuous and often wont raise any exceptions in dev because you develop and test with data,
so they slip easily in production.

    def getFirstUser():
        return User.objects.all().first()

If the QuerySet is empty it should just return None instead of raising an exception.

Other methods like .last() could probably also be useful, but getting the first element of a QuerySet
is the pitfall I see the most often.

Surprise Poney Quizz: which is the fastest way to get the first object of a QuerySet ?

A) Using count:

    qs = User.objects.all()
if qs.count() > 0:
   
return qs[0]
else:
   
return None

B) Convert to list:
    r = list(qs[:1])
if r:
 
return r[0]
return None

C) Using len:

    qs = User.objects.all()
if len(qs) > 0:
   
return qs[0]
else:
   
return None

D) Try/except:

    qs = User.objects.all()
try:
   
return qs[0]
except IndexError:
   
return None


Those examples have been taken from this post on StackOverflow (which also contains the answer):

http://stackoverflow.com/questions/5123839/fastest-way-to-get-the-first-object-from-a-queryset-in-django

Alex Gaynor

unread,
Jul 3, 2012, 12:35:36 AM7/3/12
to django-d...@googlegroups.com

--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-developers/-/JleI1VvzPXIJ.
To post to this group, send email to django-d...@googlegroups.com.
To unsubscribe from this group, send email to django-develop...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.

Actually, none of those is necessarily the correct answer.  The correct answer is going to depend on your expected result (if no results are significantly more common than results you'll probably want to use exists() first), as well as your database and the nature of the query (some databases with good optimizer recognize that that a query for equality on a unique column and optimize appropriately, thus on such a database a LIMIT is just extra bytes on the wire and for django to construct).

I'm -1 on a first() method, it strikes me as API bloat where there are 101 questions about what the most obvious API decision is, for example were we to have such a method I'd assume it'd raise Model.DoesNotExist in the absense of data, not return None.

Alex

--
"I disapprove of what you say, but I will defend to the death your right to say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero

Łukasz Rekucki

unread,
Jul 3, 2012, 1:22:59 AM7/3/12
to django-d...@googlegroups.com
E) Use __bool__ which fetches only one batch (depends on DB):

qs = User.objects.all()
user = None if not qs else qs[0]

F) Use the iterator protocol

user = next(iter(User.objects.all()), None) # you can add LIMIT or
not, see Alex's comment

--
Łukasz Rekucki

Kiril Vladimirov

unread,
Jul 3, 2012, 5:05:39 AM7/3/12
to django-d...@googlegroups.com

We shouldn't bloat the API. Obviously there are nice looking approaches and not to mention the different behaviour in some of them. 
-1 on this proposal from me.

Chris Wilson

unread,
Jul 3, 2012, 5:12:59 AM7/3/12
to django-d...@googlegroups.com
Hi all,

On Tue, 3 Jul 2012, Łukasz Rekucki wrote:
> On 3 July 2012 06:27, Maxime Haineault <hain...@gmail.com> wrote:
>> One of the common pitfall I come across way to often with novices is
>> something like this:
>>
>> def getFirstUser():
>> return User.objects.all()[0]
>>
>> It looks innocuous and often wont raise any exceptions in dev because
>> you develop and test with data, so they slip easily in production.
>>
>> def getFirstUser():
>> return User.objects.all().first()
>>
>> If the QuerySet is empty it should just return None instead of raising an
>> exception.
>>
>> Other methods like .last() could probably also be useful, but getting
>> the first element of a QuerySet is the pitfall I see the most often.

Good point. I was doing this the other day, and couldn't come up with a
nice way.

> E) Use __bool__ which fetches only one batch (depends on DB):
>
> qs = User.objects.all()
> user = None if not qs else qs[0]

Batch could be quite large, so this could still be inefficient.

> F) Use the iterator protocol
>
> user = next(iter(User.objects.all()), None) # you can add LIMIT or
> not, see Alex's comment

This is probably the most Pythonic. But none of these make the intent
really clear. I love code that's readable and obvious, and first() does it
for me. +1.

Cheers, Chris.
--
Aptivate | http://www.aptivate.org | Phone: +44 1223 967 838
Future Business, Cam City FC, Milton Rd, Cambridge, CB4 1UY, UK

Aptivate is a not-for-profit company registered in England and Wales
with company number 04980791.

Daniel Sokolowski

unread,
Jul 3, 2012, 10:42:06 AM7/3/12
to django-d...@googlegroups.com
Shouldn’t it make sense to make it:
 
User.objects.first() ?
 
I assume first() is just a custom method on the objects manager, which would mean you could chain them, and so have objects.all().first(); this also makes the code more readable in a  template:
 
{{ users.first }} as opposed to {{ users.all.0 }}
 
I am +1, and if we are doing this might as well add last(); I don’t see any harm of adding this to the API, but I agree with Alex that I expect it to raise Model.DoesNotExist for consistency.
--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-developers/-/JleI1VvzPXIJ.
To post to this group, send email to django-d...@googlegroups.com.
To unsubscribe from this group, send email to django-develop...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
 
Daniel Sokolowski
Web Engineer
Danols Web Engineering
http://webdesign.danols.com/

Luke Plant

unread,
Jul 3, 2012, 3:23:56 PM7/3/12
to django-d...@googlegroups.com
On 03/07/12 05:27, Maxime Haineault wrote:
> One of the common pitfall I come across way to often with novices is
> something like this:
>
> def getFirstUser():
> return User.objects.all()[0]
>
> It looks innocuous and often wont raise any exceptions in dev because
> you develop and test with data,
> so they slip easily in production.
>
> def getFirstUser():
> return User.objects.all().first()
>
> If the QuerySet is empty it should just return None instead of raising
> an exception.

This very similar to #2659:

https://code.djangoproject.com/ticket/2659

except added as a QuerySet method not a shortcut, and .first() wouldn't
throw an exception for multiple objects. It was WONTFIXED by Adrian.

I'm not keen on this proposal either. There are many small shortcuts you
could add, all with slightly different semantics.

As mentioned by others, the fastest way to get the first item depends on
a lot of things, and also depends on whether you are then going to get
the other items. The vast majority of the times that I get the first
item of a queryset, I will get other items as well, and for that case
.first() would make more work.

The idiom I prefer for the case where I know I will only need the first
is just this:

qs[:1]

which returns a queryset with zero or one item. In the context of data
passed into a template, it is just as easy to use as the 'item or None'
pattern provided by first():

{% if items %}
{{ items.0.something }}
{% endif %}

Luke

--
Parenthetical remarks (however relevant) are unnecessary

Luke Plant || http://lukeplant.me.uk/
Reply all
Reply to author
Forward
0 new messages