Why doesn't list have safe "get" method like dictionary?

PythonListExceptionIndexoutofboundsexception

Python Problem Overview


Why doesn't list have a safe "get" method like dictionary?

>>> d = {'a':'b'}
>>> d['a']
'b'
>>> d['c']
KeyError: 'c'
>>> d.get('c', 'fail')
'fail'

>>> l = [1]
>>> l[10]
IndexError: list index out of range

Python Solutions


Solution 1 - Python

Ultimately it probably doesn't have a safe .get method because a dict is an associative collection (values are associated with names) where it is inefficient to check if a key is present (and return its value) without throwing an exception, while it is super trivial to avoid exceptions accessing list elements (as the len method is very fast). The .get method allows you to query the value associated with a name, not directly access the 37th item in the dictionary (which would be more like what you're asking of your list).

Of course, you can easily implement this yourself:

def safe_list_get (l, idx, default):
  try:
    return l[idx]
  except IndexError:
    return default

You could even monkeypatch it onto the __builtins__.list constructor in __main__, but that would be a less pervasive change since most code doesn't use it. If you just wanted to use this with lists created by your own code you could simply subclass list and add the get method.

Solution 2 - Python

This works if you want the first element, like my_list.get(0)

>>> my_list = [1,2,3]
>>> next(iter(my_list), 'fail')
1
>>> my_list = []
>>> next(iter(my_list), 'fail')
'fail'

I know it's not exactly what you asked for but it might help others.

Solution 3 - Python

Probably because it just didn't make much sense for list semantics. However, you can easily create your own by subclassing.

class safelist(list):
    def get(self, index, default=None):
        try:
            return self.__getitem__(index)
        except IndexError:
            return default

def _test():
    l = safelist(range(10))
    print l.get(20, "oops")

if __name__ == "__main__":
    _test()

Solution 4 - Python

Instead of using .get, using like this should be ok for lists. Just a usage difference.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'

Solution 5 - Python

Credits to jose.angel.jimenez and Gus Bus.


For the "oneliner" fans…


If you want the first element of a list or if you want a default value if the list is empty try:

liste = ['a', 'b', 'c']
value = (liste[0:1] or ('default',))[0]
print(value)

returns a

and

liste = []
value = (liste[0:1] or ('default',))[0]
print(value)

returns default


Examples for other elements…

liste = ['a', 'b', 'c']
print(liste[0:1])  # returns ['a']
print(liste[1:2])  # returns ['b']
print(liste[2:3])  # returns ['c']
print(liste[3:4])  # returns []

With default fallback…

liste = ['a', 'b', 'c']
print((liste[0:1] or ('default',))[0])  # returns a
print((liste[1:2] or ('default',))[0])  # returns b
print((liste[2:3] or ('default',))[0])  # returns c
print((liste[3:4] or ('default',))[0])  # returns default

Possibly shorter:

liste = ['a', 'b', 'c']
value, = liste[:1] or ('default',)
print(value)  # returns a

It looks like you need the comma before the equal sign, the equal sign and the latter parenthesis.


More general:

liste = ['a', 'b', 'c']
f = lambda l, x, d: l[x:x+1] and l[x] or d
print(f(liste, 0, 'default'))  # returns a
print(f(liste, 1, 'default'))  # returns b
print(f(liste, 2, 'default'))  # returns c
print(f(liste, 3, 'default'))  # returns default

Tested with Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)

Solution 6 - Python

Try this:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'

Solution 7 - Python

A reasonable thing you can do is to convert the list into a dict and then access it with the get method:

>>> my_list = ['a', 'b', 'c', 'd', 'e']
>>> my_dict = dict(enumerate(my_list))
>>> print my_dict
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
>>> my_dict.get(2)
'c'
>>> my_dict.get(10, 'N/A')

Solution 8 - Python

So I did some more research into this and it turns out there isn't anything specific for this. I got excited when I found list.index(value), it returns the index of a specified item, but there isn't anything for getting the value at a specific index. So if you don't want to use the safe_list_get solution which I think is pretty good. Here are some 1 liner if statements that can get the job done for you depending on the scenario:

>>> x = [1, 2, 3]
>>> el = x[4] if len(x) > 4 else 'No'
>>> el
'No'

You can also use None instead of 'No', which makes more sense.:

>>> x = [1, 2, 3]
>>> i = 2
>>> el_i = x[i] if len(x) == i+1 else None

Also if you want to just get the first or last item in the list, this works

end_el = x[-1] if x else None

You can also make these into functions but I still liked the IndexError exception solution. I experimented with a dummied down version of the safe_list_get solution and made it a bit simpler (no default):

def list_get(l, i):
    try:
        return l[i]
    except IndexError:
        return None

Haven't benchmarked to see what is fastest.

Solution 9 - Python

Dictionaries are for look ups. It makes sense to ask if an entry exists or not. Lists are usually iterated. It isn't common to ask if L[10] exists but rather if the length of L is 11.

Solution 10 - Python

If you

  1. want a one liner,
  2. prefer not having try / except in your happy code path where you needn't, and
  3. want the default value to be optional,

you can use this:

list_get = lambda l, x, d=None: d if not l[x:x+1] else l[x]

Usage looks like:

>>> list_get(['foo'], 4) == None
True
>>> list_get(['hootenanny'], 4, 'ho down!')
'ho down!'
>>> list_get([''], 0)
''

Solution 11 - Python

This isn't an extremely general-purpose solution, but I had a case where I expected a list of length 3 to 5 (with a guarding if), and I was breaking out the values to named variables. A simple and concise way I found for this involved:

foo = (argv + [None, None])[3]
bar = (argv + [None, None])[4]

Now foo and bar are either the 4th and 5th values in the list, or None if there weren't that many values.

Solution 12 - Python

For small index values you can implement

my_list.get(index, default)

as

(my_list + [default] * (index + 1))[index]

If you know in advance what index is then this can be simplified, for example if you knew it was 1 then you could do

(my_list + [default, default])[index]

Because lists are forward packed the only fail case we need to worry about is running off the end of the list. This approach pads the end of the list with enough defaults to guarantee that index is covered.

Solution 13 - Python

Your usecase is basically only relevant for when doing arrays and matrixes of a fixed length, so that you know how long they are before hand. In that case you typically also create them before hand filling them up with None or 0, so that in fact any index you will use already exists.

You could say this: I need .get() on dictionaries quite often. After ten years as a full time programmer I don't think I have ever needed it on a list. :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMikhail M.View Question on Stackoverflow
Solution 1 - PythonNick BastinView Answer on Stackoverflow
Solution 2 - PythonJakeView Answer on Stackoverflow
Solution 3 - PythonKeithView Answer on Stackoverflow
Solution 4 - PythonYOUView Answer on Stackoverflow
Solution 5 - PythonqräbnöView Answer on Stackoverflow
Solution 6 - PythonVsevolod KulagaView Answer on Stackoverflow
Solution 7 - PythonfabView Answer on Stackoverflow
Solution 8 - PythonradtekView Answer on Stackoverflow
Solution 9 - PythonApprentice QueueView Answer on Stackoverflow
Solution 10 - PythonGus BusView Answer on Stackoverflow
Solution 11 - PythonarantiusView Answer on Stackoverflow
Solution 12 - PythonGordon WrigleyView Answer on Stackoverflow
Solution 13 - PythonLennart RegebroView Answer on Stackoverflow