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

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

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.

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.

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

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'

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)

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

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

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.

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.

If you
want a one liner,
prefer not having try / except in your happy code path where you needn't, and
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)
''

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.

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.

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. :)

Related

Is it possible to use map on methods like List.append

I am wondering if you can use mapping like this:
x = ["a"]
y = ["b","c","d"]
map(x.append,y)
I would like x = ["a","b","c","d"], However this does not happen
map returns an iterator, so nothing happens as long as you don't iterate on it. You could make that happen by calling list on it:
x = ["a"]
y = ["b","c","dx"]
list(map(x.extend,y))
# [None, None, None]
print(x)
# ['a', 'b', 'c', 'd', 'x']
Note that, as extend expects an iterable (which str objects are), it will iterate on each string in y and append the characters, so you'll get 'd' and 'x' in the example above.
You probably meant to use append:
x = ["a"]
y = ["b","c","dx"]
list(map(x.append,y))
# [None, None, None]
print(x)
# ['a', 'b', 'c', 'dx']
But anyway, map is meant to yield the results of applying the function to the items of the iterable (here, the None returned by x.append()), and using it for the side effect of the function makes things rather unclear.
x = ["a"]
y = ["b","c","d"]
x.extend(y)
print (x)
output:
['a', 'b', 'c', 'd']
The extend() extends the list by adding all items of a list (passed as an argument) to the end.
The syntax of extend() method is:
list1.extend(list2)
Here, the elements of list2 are added to the end of list1.
Extend function takes a list as input as I know. With map() it would try to take an element, since map gives elements of an iterable to a function one by one and returns the result as a list again.
This should do
x.extend(y)
Any reason for doing this?
map is a worst choice in case of readability and execution speed.
x = ["a"]
y = ["b","c","d"]
x.extend(y)

How to mutate a list with a function in python?

Here's a pseudocode I've written describing my problem:-
func(s):
#returns a value of s
x = a list of strings
print func(x)
print x #these two should give the SAME output
When I print the value of x in the end, I want it to be the one returned by func(x). Can I do something like this only by editing the function (and without setting x = func(x))
func(s):
s[:] = whatever after mutating
return s
x = a list of strings
print func(x)
print x
You don't actually need to return anything:
def func(s):
s[:] = [1,2,3]
x = [1,2]
print func(x)
print x # -> [1,2,3]
It all depends on what you are actually doing, appending or any direct mutation of the list will be reflected outside the function as you are actually changing the original object/list passed in. If you were doing something that created a new object and you wanted the changes reflected in the list passed in setting s[:] =.. will change the original list.
That's already how it behaves, the function can mutate the list
>>> l = ['a', 'b', 'c'] # your list of strings
>>> def add_something(x): x.append('d')
...
>>> add_something(l)
>>> l
['a', 'b', 'c', 'd']
Note however that you cannot mutate the original list in this manner
def modify(x):
x = ['something']
(The above will assign x but not the original list l)
If you want to place a new list in your list, you'll need something like:
def modify(x):
x[:] = ['something']

How to unpack list into two variables in python2.x

I would like to unpack the return of a function into :
a first variable always set up by the first returned value
a second variable to store any exceeded returned value
To do so, I have this code working under python3.x. How could I make it works with python 2.x (python2.6 at least) ?
a,*b = ['a','b','c']
Edit: This would also work with :
a,*b = ['a']
There is no straight forward way to do this in Python 2.7, instead you can create a new list without the first element and first element alone and unpack them into respective variables.
data = ['a','b','c']
a, b = data[0], data[1:]
print a, b
Output
a ['b', 'c']
This solution will still work, even if the RHS has only one element
data = ['a']
a, b = data[0], data[1:]
print a, b
Output
a []
Use slicing:
>>> lis = ['a','b','c']
>>> a, b = lis[0], lis[1:]
>>> a
'a'
>>> b
['b', 'c']

Cannot append to a returned list?

def f():
lst = ['a', 'b', 'c']
return lst[1:]
why is f().append('a') is None == True even though f().__class__ is <type 'list'> and f() == ['b', 'c']
Because append() returns None and not the list object. Use
l = f()
l.append('a')
...
Because append() modifies the list, but does not return it.
Try this:
f()+['a']
Hope this helps
In this context it's always good to be fully aware of the difference between expressions and commands. There are basically two ways to append a value x to a list l
Using a command: l.append(x). Usually a command doesn't return any value; it performs some kind of side-effect.
Using an expression, namely l+[x] which stands for a value and does nothing. I.e. you assign l=l+[x]

Python: return the index of the first element of a list which makes a passed function true

The list.index(x) function returns the index in the list of the first item whose value is x.
Is there a function, list_func_index(), similar to the index() function that has a function, f(), as a parameter. The function, f() is run on every element, e, of the list until f(e) returns True. Then list_func_index() returns the index of e.
Codewise:
>>> def list_func_index(lst, func):
for i in range(len(lst)):
if func(lst[i]):
return i
raise ValueError('no element making func True')
>>> l = [8,10,4,5,7]
>>> def is_odd(x): return x % 2 != 0
>>> list_func_index(l,is_odd)
3
Is there a more elegant solution? (and a better name for the function)
You could do that in a one-liner using generators:
next(i for i,v in enumerate(l) if is_odd(v))
The nice thing about generators is that they only compute up to the requested amount. So requesting the first two indices is (almost) just as easy:
y = (i for i,v in enumerate(l) if is_odd(v))
x1 = next(y)
x2 = next(y)
Though, expect a StopIteration exception after the last index (that is how generators work). This is also convenient in your "take-first" approach, to know that no such value was found --- the list.index() function would throw ValueError here.
One possibility is the built-in enumerate function:
def index_of_first(lst, pred):
for i,v in enumerate(lst):
if pred(v):
return i
return None
It's typical to refer a function like the one you describe as a "predicate"; it returns true or false for some question. That's why I call it pred in my example.
I also think it would be better form to return None, since that's the real answer to the question. The caller can choose to explode on None, if required.
#Paul's accepted answer is best, but here's a little lateral-thinking variant, mostly for amusement and instruction purposes...:
>>> class X(object):
... def __init__(self, pred): self.pred = pred
... def __eq__(self, other): return self.pred(other)
...
>>> l = [8,10,4,5,7]
>>> def is_odd(x): return x % 2 != 0
...
>>> l.index(X(is_odd))
3
essentially, X's purpose is to change the meaning of "equality" from the normal one to "satisfies this predicate", thereby allowing the use of predicates in all kinds of situations that are defined as checking for equality -- for example, it would also let you code, instead of if any(is_odd(x) for x in l):, the shorter if X(is_odd) in l:, and so forth.
Worth using? Not when a more explicit approach like that taken by #Paul is just as handy (especially when changed to use the new, shiny built-in next function rather than the older, less appropriate .next method, as I suggest in a comment to that answer), but there are other situations where it (or other variants of the idea "tweak the meaning of equality", and maybe other comparators and/or hashing) may be appropriate. Mostly, worth knowing about the idea, to avoid having to invent it from scratch one day;-).
Not one single function, but you can do it pretty easily:
>>> test = lambda c: c == 'x'
>>> data = ['a', 'b', 'c', 'x', 'y', 'z', 'x']
>>> map(test, data).index(True)
3
>>>
If you don't want to evaluate the entire list at once you can use itertools, but it's not as pretty:
>>> from itertools import imap, ifilter
>>> from operator import itemgetter
>>> test = lambda c: c == 'x'
>>> data = ['a', 'b', 'c', 'x', 'y', 'z']
>>> ifilter(itemgetter(1), enumerate(imap(test, data))).next()[0]
3
>>>
Just using a generator expression is probably more readable than itertools though.
Note in Python3, map and filter return lazy iterators and you can just use:
from operator import itemgetter
test = lambda c: c == 'x'
data = ['a', 'b', 'c', 'x', 'y', 'z']
next(filter(itemgetter(1), enumerate(map(test, data))))[0] # 3
A variation on Alex's answer. This avoids having to type X every time you want to use is_odd or whichever predicate
>>> class X(object):
... def __init__(self, pred): self.pred = pred
... def __eq__(self, other): return self.pred(other)
...
>>> L = [8,10,4,5,7]
>>> is_odd = X(lambda x: x%2 != 0)
>>> L.index(is_odd)
3
>>> less_than_six = X(lambda x: x<6)
>>> L.index(less_than_six)
2
you could do this with a list-comprehension:
l = [8,10,4,5,7]
filterl = [a for a in l if a % 2 != 0]
Then filterl will return all members of the list fulfilling the expression a % 2 != 0. I would say a more elegant method...
Intuitive one-liner solution:
i = list(map(lambda value: value > 0, data)).index(True)
Explanation:
we use map function to create a list containing True or False based on if each element in our list pass the condition in the lambda or not.
then we convert the map output to list
then using the index function, we get the index of the first true which is the same index of the first value passing the condition.

Categories