views:

1625

answers:

14

I'm sure there's a simpler way of doing this that's just not occurring to me.

I'm calling a bunch of methods that return a list. The list may be empty. If the list is non-empty, I want to return the first item; otherwise, I want to return None. This code works:

list = get_list()
if len(list) > 0: return list[0]
return None

It seems to me that there should be a simple one-line idiom for doing this, but for the life of me I can't think of it. Is there?

Edit:

The reason that I'm looking for a one-line expression here is not that I like incredibly terse code, but because I'm having to write a lot of code like this:

x = get_first_list()
if x:
    # do something with x[0]
    # inevitably forget the [0] part, and have a bug to fix
y = get_second_list()
if y:
    # do something with y[0]
    # inevitably forget the [0] part AGAIN, and have another bug to fix

What I'd like to be doing can certainly be accomplished with a function (and probably will be):

def first_item(list_or_none):
    if list_or_none: return list_or_none[0]

x = first_item(get_first_list())
if x:
    # do something with x
y = first_item(get_second_list())
if y:
    # do something with y

I posted the question because I'm frequently surprised by what simple expressions in Python can do, and I thought that writing a function was a silly thing to do if there was a simple expression could do the trick. But seeing these answers, it seems like a function is the simple solution.

+4  A: 
(get_list()[0:1] or [None])[0]

That should work, but it's a mess.

BTW I didn't use the variable list, because that overwrites the builtin list() function.

Edit: I had a slightly simpler, but wrong version here earlier.

Here's another approach:

(get_list() + [None])[0]
recursive
This is clever and works, but I think "return foo[0] if foo else None" as suggested by efotinis is a little easier to follow for maintenance.
Jay
The question was asking for a one line solution, so I ruled that out.
recursive
Neither of those expressions work if get_list() returns None; you get "TypeError: 'NoneType' object is unsubscriptable." or "TypeError: unsupported operand type(s) for +: 'NoneType' and 'list'."
Robert Rossney
In the question, it says that the methods return lists, of which None is not one. So given the requirements, I still believe they both work.
recursive
My mistake: you're quite right.
Robert Rossney
A: 

You could use Extract Method. In other words extract that code into a method which you'd then call.

I wouldn't try to compress it much more, the one liners seem harder to read than the verbose version. And if you use Extract Method, it's a one liner ;)

Stephane Grenier
+12  A: 

The best way is this:

a = get_list()
return a[0] if a else None

You could also do it in one line, but it's much harder for the programmer to read:

return (get_list()[:1] or [None])[0]
efotinis
Oops. Should have mentioned this is a Python 2.4 app.
Robert Rossney
A: 
try:
    return a[0]
except IndexError:
    return None
limscoder
Exceptions generally aren't used for flow control.
Matt Green
s/aren't/shouldn't be/ .... unfortunately
Javier
I don't think making this code longer and adding exception-handling to it is exactly the idiom I was looking for.
Robert Rossney
Agree with Matt and Javier.
gotgenes
But this is a common Python idiom! For performance reasons, you might not use it; you will get better performance out of `if len(a) > 0:` but this is considered good style. Note how well this expresses the problem: "try to extract the head of a list, and if that doesn't work out, return `None`". Google search for "EAFP" or look here: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#eafp-try-except-example
steveha
+1  A: 

For the heck of it, here's yet another possibility.

return None if not get_list() else get_list()[0]

Benefit: This method handles the case where get_list is None, without using try/except or assignment. To my knowledge, none of the implementations above can handle this possibility

Downfalls: get_list() is called twice, quite unnecessarily, especially if the list is long and/or created when the function is called.

The truth is, it's more "Pythonic" in my opinion, to provide code that is readable than it is to make a one-liner just because you can :) I have to admit I am guilty many times of unnecessarily compacting Python code just because I'm so impressed how small I can make a complex function look :)

Edit: As user "hasen j" commented below, the conditional expression above is new in Python 2.5, as described here: http://www.python.org/doc/2.5/whatsnew/pep-308.html. Thanks, hasen!

Chris Cameron
you should add a note that this only works in 2.5 +
hasen j
That calls get_list() twice, which is usually a bad idea.
recursive
+2  A: 
for item in get_list():
    return item
Coady
This is succinct and beautiful.
gotgenes
Tragically, it doesn't work; it throws a "TypeError: 'NoneType' object is not iterable" exception if get_list() returns None.
Robert Rossney
To fix: "for item in get_list() or []:"
itsadok
The question clearly presumes get_list returns a list. len and __getitem__ are called on its result.
Coady
A: 

isn't the idiomatic python equivalent to C-style ternary operators

cond and true_expr or false_expr

ie.

list = get_list()
return list and list[0] or None
Jimmy
If a[0] is the number 0 or the empty string (or anything else that evaluates to false), this will return None instead of what a[0] actually is.
Adam Rosenfield
ha, you're right.
Jimmy
+6  A: 

The OP's solution is nearly there, there are just a few things to make it more Pythonic.

For one, there's no need to get the length of the list. Empty lists in Python evaluate to False in an if check. Just simply say

if list:

Additionally, it's a very Bad Idea to assign to variables that overlap with reserved words. "list" is a reserved word in Python.

So let's change that to

some_list = get_list()
if some_list:

A really important point that a lot of solutions here miss is that all Python functions/methods return None by default. Try the following below.

def does_nothing():
    pass

foo = does_nothing()
print foo

Unless you need to return None to terminate a function early, it's unnecessary to explicitly return None. Quite succinctly, just return the first entry, should it exist.

some_list = get_list()
if some_list:
    return list[0]

And finally, perhaps this was implied, but just to be explicit (because explicit is better than implicit), you should not have your function get the list from another function; just pass it in as a parameter. So, the final result would be

def get_first_item(some_list): 
    if some_list:
        return list[0]

my_list = get_list()
first_item = get_first_item(my_list)

As I said, the OP was nearly there, and just a few touches give it the Python flavor you're looking for.

gotgenes
Why is 'list' a reserved word in python? Or, more to the point, where does it say that 'list' is a reserver word in python? Or, more to the point, how did you verify that 'list' is a reserved word in python? Considering that I can declare a variable named 'list', it is *clearly* not a reserved word.
Lasse V. Karlsen
Point taken. The truly reserved words in Python can be found here http://tinyurl.com/424663 However, it's a good idea to consider builtin functions and types as reserved. list() actually generates a list. Same goes for dict, range, etc.
gotgenes
I would combine the last two lines, eliminating the temporary variable (unless you need my_list further). That improves readability.
Svante
This is pretty much the answer I was gravitating towards.
Robert Rossney
`get_first_item` should accept a `default` parameter (like `dict.get`) which defaults itself to None. Thus the function interface communicates explicitly what happens if `my_list` is empty.
J.F. Sebastian
I've posted get_first() implementation as an answer http://stackoverflow.com/questions/363944/python-idiom-to-return-first-item-or-none#365934
J.F. Sebastian
+1  A: 

Frankly speaking, I do not think there is a better idiom: your is clear and terse - no need for anything "better". Maybe, but this is really a matter of taste, you could change if len(list) > 0: with if list: - an empty list will always evaluate to False.

On a related note, Python is not Perl (no pun intended!), you do not have to get the coolest code possible.
Actually, the worst code I have seen in Python, was also very cool :-) and completely unmaintainable.

By the way, most of the solution I have seen here do not take into consideration when list[0] evaluates to False (e.g. empty string, or zero) - in this case, they all return None and not the correct element.

Roberto Liffredo
In Python, it is considered good style to write `if lst:` rather than `if len(lst) > 0:`.Also, don't use Python keywords as variable names; it ends in tears. It's common to use `L` or `lst` as the variable name for some list. I'm sure in this case you didn't mean to suggest actually using `list` as a variable name, you just meant "some list".And, +1 for "when lst[0] evaluates to False".
steveha
Yes, I was just maintaining the same kind of name of OP, for better clarity. But you are definitely right: some care should be always be taken not to override Python keywords.
Roberto Liffredo
A: 

Using the and-or trick:

a = get_list()
return a and a[0] or None
titaniumdecoy
A: 

Several people have suggested doing something like this:

list = get_list()
return list and list[0] or None

That works in many cases, but it will only work if list[0] is not equal to 0, False, or an empty string. If list[0] is 0, False, or an empty string, the method will incorrectly return None.

(I've created this bug in my own code one too many times!)

Clint Miller
+3  A: 
def get_first(iterable, default=None):
    if iterable:
        for item in iterable:
            return item
    return default

Example:

x = get_first(get_first_list())
if x:
    ...
y = get_first(get_second_list())
if y:
    ...

Another option is to inline the above function:

for x in get_first_list() or []:
    # process x
    break # process at most one item
for y in get_second_list() or []:
    # process y
    break
J.F. Sebastian
That last option is almost exactly what I'm looking for: it's clear, it works, and it doesn't require me to define a new function. I'd say "exactly" if the break were somehow not needed, because the risk of omitting it is not insignificant. But this approach has the ring of truth.
Robert Rossney
Upvoted for the for loop suggestion. You should really split the answer.
itsadok
Oh, I don't like this at all. If any item in the list evaluates False, that value is discarded and replaced. If you have an empty string `""` in the list, that is discarded and replaced by an empty list `[]`. If you have a 0, also replaced by `[]`. If you have `False` in there, also replaced. Etc. You might get away with this in a specific case, but this is a bad habit to develop.
steveha
@steveha: `bool(lst) `tells us whether `len(lst) > 0` it doesn't tell us anything about what items `lst` contains e.g., `bool([False]) == True;` therefore the expression `[False] or []` returns `[False]`, the same is for `[0] or [] ` it returns `[0]`.
J.F. Sebastian
A: 

Out of curiosity, I ran timings on two of the solutions. The solution which uses a return statement to prematurely end a for loop is slightly more costly on my machine with Python 2.5.1, I suspect this has to do with setting up the iterable.

import random
import timeit

def index_first_item(some_list):
    if some_list:
        return some_list[0]


def return_first_item(some_list):
    for item in some_list:
        return item


empty_lists = []
for i in range(10000):
    empty_lists.append([])

assert empty_lists[0] is not empty_lists[1]

full_lists = []
for i in range(10000):
    full_lists.append(list([random.random() for i in range(10)]))

mixed_lists = empty_lists[:50000] + full_lists[:50000]
random.shuffle(mixed_lists)

if __name__ == '__main__':
    ENV = 'import firstitem'
    test_data = ('empty_lists', 'full_lists', 'mixed_lists')
    funcs = ('index_first_item', 'return_first_item')
    for data in test_data:
        print "%s:" % data
        for func in funcs:
            t = timeit.Timer('firstitem.%s(firstitem.%s)' % (
                func, data), ENV)
            times = t.repeat()
            avg_time = sum(times) / len(times)
            print "  %s:" % func
            for time in times:
                print "    %f seconds" % time
            print "    %f seconds avg." % avg_time

These are the timings I got:

empty_lists:
  index_first_item:
    0.748353 seconds
    0.741086 seconds
    0.741191 seconds
    0.743543 seconds avg.
  return_first_item:
    0.785511 seconds
    0.822178 seconds
    0.782846 seconds
    0.796845 seconds avg.
full_lists:
  index_first_item:
    0.762618 seconds
    0.788040 seconds
    0.786849 seconds
    0.779169 seconds avg.
  return_first_item:
    0.802735 seconds
    0.878706 seconds
    0.808781 seconds
    0.830074 seconds avg.
mixed_lists:
  index_first_item:
    0.791129 seconds
    0.743526 seconds
    0.744441 seconds
    0.759699 seconds avg.
  return_first_item:
    0.784801 seconds
    0.785146 seconds
    0.840193 seconds
    0.803380 seconds avg.
gotgenes
A: 
def head(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None

print head(xrange(42, 1000)  # 42
print head([])               # None

BTW: I'd rework your general program flow into something like this:

lists = [
    ["first", "list"],
    ["second", "list"],
    ["third", "list"]
]

def do_something(element):
    if not element:
        return
    else:
        # do something
        pass

for li in lists:
    do_something(head(li))

(Avoiding repetition whenever possible)

Tim