Самый элегантный способ изменения элементов вложенных списков

У меня есть 2D-список, который выглядит так:

table = [['donkey', '2', '1', '0'], ['goat', '5', '3', '2']]

Я хочу изменить последние три элемента на целые числа, но приведенный ниже код кажется очень уродливым:

for row in table: for i in range(len(row)-1): row[i+1] = int(row[i+1])

Но я бы предпочел бы что-то вроде:

for row in table: for col in row[1:]: col = int(col)

Я думаю, что должен быть способ написать выше код, но срез создает итератор/новый список, который отделен от оригинала, поэтому ссылки не переносятся.

Есть ли способ получить более Pythonic-решение?

7 ответов

for row in table: row[1:] = [int(c) for c in row[1:]]

Выше выглядит больше pythonic?


Try:

>>> for row in table:
... row[1:]=map(int,row[1:])
...
>>> table
[['donkey', 2, 1, 0], ['goat', 5, 3, 2]]

AFAIK, назначая срезу list, заставляет операцию выполнять на месте вместо создания нового list.


Мне нравится, что Шехар много отвечает.

Как правило, при написании кода Python, если вы обнаруживаете, что пишете f or i in range(len(somelist)), вы делаете это неправильно:

  • попробуйте enumerate, если у вас есть один список
  • попробуйте zip или itertools.izip, если у вас есть 2 или более списков, которые вы хотите повторить параллельно.

В вашем случае первый столбец отличается тем, что вы не можете элегантно использовать перечисление:

for row in table: for i, val in enumerate(row): if i == 0: continue row[i] = int(val)


Ваш "уродливый" код можно улучшить, просто позвонив range с двумя аргументами:

for row in table: for i in range(1, len(row)): row[i] = int(row[i])

Это, пожалуй, самое лучшее, что вы можете сделать, если настаиваете на изменении элементов на месте, не выделяя новые временные списки (используя понимание списка, map и/или нарезку). См. Есть ли эквивалент "map" на месте в python?

Хотя я не рекомендую его, вы также можете сделать этот код более общим, представив свою собственную функцию карты на месте:

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)

Лично я нахожу это менее Pythonic. Существует только один очевидный способ сделать это, и это не так.


Использовать список:

table = [row[0] + [int(col) for col in row[1:]] for row in table]


Это будет работать:

table = [[row[0]] + [int(v) for v in row[1:]] for row in table]

Однако вы можете подумать о выполнении преобразования в том месте, где сначала создается список.


Выполняет то, что вы ищете. Это читаемое решение. Вы можете пойти на аналогичный, используя listcomp тоже.

>>> for row in table:
... for i, elem in enumerate(row):
... try:
... int(elem)
... except ValueError:
... pass
... else:
... row[i] = int(elem)
...

licensed under cc by-sa 3.0 with attribution.