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

Question:

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
Asked By: Mikhail M.

||

Answers:

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.

Answered By: Nick Bastin

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.

Answered By: Apprentice Queue

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()
Answered By: Keith

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'
Answered By: YOU

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. 🙂

Answered By: Lennart Regebro

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.

Answered By: Jake

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.

Answered By: radtek

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')
Answered By: fab

Try this:

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

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)

Answered By: qräbnö

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)
''
Answered By: Gus Bus

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.

Answered By: Gordon Wrigley

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.

Answered By: arantius