Python idiom to return first item or None

冷暖自知 提交于 2019-11-27 09:01:00

问题


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:

my_list = get_list()
if len(my_list) > 0: return my_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.


回答1:


Python 2.6+

next(iter(your_list), None)

If your_list can be None:

next(iter(your_list or []), None)

Python 2.4

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

To avoid break you could write:

for x in yield_first(get_first_list()):
    x # process x
for y in yield_first(get_second_list()):
    y # process y

Where:

def yield_first(iterable):
    for item in iterable or []:
        yield item
        return



回答2:


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]



回答3:


(get_list() or [None])[0]

That should work.

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.




回答4:


The most python idiomatic way is to use the next() on a iterator since list is iterable. just like what @J.F.Sebastian put in the comment on Dec 13, 2011.

next(iter(the_list), None) This returns None if the_list is empty. see next() Python 2.6+

or if you know for sure the_list is not empty:

iter(the_list).next() see iterator.next() Python 2.2+




回答5:


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.




回答6:


If you find yourself trying to pluck the first thing (or None) from a list comprehension you can switch to a generator to do it like:

next((x for x in blah if cond), None)

Pro: works if blah isn't indexable Con: it's unfamiliar syntax. It's useful while hacking around and filtering stuff in ipython though.




回答7:


for item in get_list():
    return item



回答8:


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.




回答9:


Python idiom to return first item or None?

The most Pythonic approach is what the most upvoted answer demonstrated, and it was the first thing to come to my mind when I read the question. Here's how to use it, first if the possibly empty list is passed into a function:

def get_first(l): 
    return l[0] if l else None

And if the list is returned from a get_list function:

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

Other ways demonstrated to do this here, with explanations

for

When I began trying to think of clever ways to do this, this is the second thing I thought of:

for item in get_list():
    return item

This presumes the function ends here, implicitly returning None if get_list returns an empty list. The below explicit code is exactly equivalent:

for item in get_list():
    return item
return None

if some_list

The following was also proposed (I corrected the incorrect variable name) which also uses the implicit None. This would be preferable to the above, as it uses the logical check instead of an iteration that may not happen. This should be easier to understand immediately what is happening. But if we're writing for readability and maintainability, we should also add the explicit return None at the end:

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

slice or [None] and select zeroth index

This one is also in the most up-voted answer:

return (get_list()[:1] or [None])[0]

The slice is unnecessary, and creates an extra one-item list in memory. The following should be more performant. To explain, or returns the second element if the first is False in a boolean context, so if get_list returns an empty list, the expression contained in the parentheses will return a list with 'None', which will then be accessed by the 0 index:

return (get_list() or [None])[0]

The next one uses the fact that and returns the second item if the first is True in a boolean context, and since it references my_list twice, it is no better than the ternary expression (and technically not a one-liner):

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

next

Then we have the following clever use of the builtin next and iter

return next(iter(get_list()), None)

To explain, iter returns an iterator with a .next method. (.__next__ in Python 3.) Then the builtin next calls that .next method, and if the iterator is exhausted, returns the default we give, None.

redundant ternary expression (a if b else c) and circling back

The below was proposed, but the inverse would be preferable, as logic is usually better understood in the positive instead of the negative. Since get_list is called twice, unless the result is memoized in some way, this would perform poorly:

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

The better inverse:

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

Even better, use a local variable so that get_list is only called one time, and you have the recommended Pythonic solution first discussed:

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



回答10:


Regarding idioms, there is an itertools recipe called nth.

From itertools recipes:

def nth(iterable, n, default=None):
    "Returns the nth item or a default value"
    return next(islice(iterable, n, None), default)

If you want one-liners, consider installing a library that implements this recipe for you, e.g. more_itertools:

import more_itertools as mit

mit.nth([3, 2, 1], 0)
# 3

mit.nth([], 0)                                             # default is `None`
# None

Another tool is available that only returns the first item, called more_itertools.first.

mit.first([3, 2, 1])
# 3

mit.first([], default=None)
# None

These itertools scale generically for any iterable, not only for lists.




回答11:


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.



回答12:


try:
    return a[0]
except IndexError:
    return None



回答13:


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)




回答14:


How about this:

(my_list and my_list[0]) or None

Note: This should work fine for lists of objects but it might return incorrect answer in case of number or string list per the comments below.




回答15:


my_list[0] if len(my_list) else None




回答16:


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 ;)




回答17:


Using the and-or trick:

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



回答18:


And what about: next(iter(get_list()), None)? Might not be the fastest one here, but is standard (starting from Python 2.6) and succinct.




回答19:


Probably not the fastest solution, but nobody mentioned this option:

dict(enumerate(get_list())).get(0)

if get_list() can return None you can use:

dict(enumerate(get_list() or [])).get(0)

Advantages:

-one line

-you just call get_list() once

-easy to understand




回答20:


My use case was only to set the value of a local variable.

Personally I found the try and except style cleaner to read

items = [10, 20]
try: first_item = items[0]
except IndexError: first_item = None
print first_item

than slicing a list.

items = [10, 20]
first_item = (items[:1] or [None, ])[0]
print first_item



回答21:


if mylist != []:

       print(mylist[0])

   else:

       print(None)



回答22:


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 !




回答23:


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


来源:https://stackoverflow.com/questions/363944/python-idiom-to-return-first-item-or-none

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!