I have a 2D list that looks like this:
table = [['donkey', '2', '1', '0'], ['goat', '5', '3', '2']]
I want to change the last three elements to integers, but the code below feels very ugly:
for row in table: for i in range(len(row)-1): row[i+1] = int(row[i+1])
But I'd rather have something that looks like:
for row in table: for col in row[1:]: col = int(col)
I think there should be a way to write the code above, but the slice creates an iterator/new list that's separate from the original, so the references don't carry over.
Is there some way to get a more Pythonic solution?
for row in table: row[1:] = [int(c) for c in row[1:]]
Does above look more pythonic?
>>> for row in table: ... row[1:]=map(int,row[1:]) ... >>> table [['donkey', 2, 1, 0], ['goat', 5, 3, 2]]
AFAIK, assigning to a
listslice forces the operation to be done in place instead of creating a new
I like Shekhar answer a lot.
As a general rule, when writing Python code, if you find yourself writing f
or i in range(len(somelist)), you're doing it wrong:
enumerateif you have a single list
itertools.izipif you have 2 or more lists you want to iterate on in parallel
In your case, the first column is different so you cannot elegantly use enumerate:
for row in table: for i, val in enumerate(row): if i == 0: continue row[i] = int(val)
Your "ugly" code can be improved just by calling
rangewith two arguments:
for row in table: for i in range(1, len(row)): row[i] = int(row[i])
This is probably the best you can do if you insist on changing the items in place without allocating new temporary lists (either by using a list comprehension,
map, and/or slicing). SeeIs there an in-place equivalent to 'map' in python?
Although I don't recommend it, you can also make this code more general by introducing your own in-place map function:
def inplacemap(f, items, start=0, end=None): """Applies ``f`` to each item in the iterable ``items`` between the range ``start`` and ``end``.""" # If end was not specified, make it the length of the iterable # We avoid setting end in the parameter list to force it to be evaluated on # each invocation if end is None: end = len(items) for i in range(start, end): items[i] = f(items[i]) for row in table: inplacemap(int, row, 1)
Personally, I find thislessPythonic. There is preferably only one obvious way to do it, and this isn't it.
Use list comprehensions:
table = [row + [int(col) for col in row[1:]] for row in table]
This will work:
table = [[row] + [int(v) for v in row[1:]] for row in table]
However you might want to think about doing the conversion at the point where the list is first created.
This accomplishes what you are looking for. It is a readable solution. You can go for similar one using listcomp too.
>>> for row in table: ... for i, elem in enumerate(row): ... try: ... int(elem) ... except ValueError: ... pass ... else: ... row[i] = int(elem) ...