8

Создать список из одного элемента, повторенного N раз

1

Я хочу создать серию списков, каждый раз разной длины. Каждый список должен содержать один и тот же элемент <code>e</code>, повторенный <code>n</code> раз (где <code>n</code> — это длина списка).

Как мне создать эти списки, не используя списковое включение <code>[e for number in range(n)]</code> для каждого списка?

5 ответ(ов)

11

Вы также можете записать:

[e] * n

Стоит отметить, что если e, например, является пустым списком, то вы получите список, содержащий n ссылок на один и тот же список, а не n независимых пустых списков.

Тестирование производительности

На первый взгляд кажется, что itertools.repeat — самый быстрый способ создать список из n одинаковых элементов:

>>> timeit.timeit('itertools.repeat(0, 10)', 'import itertools', number=1000000)
0.37095273281943264
>>> timeit.timeit('[0] * 10', 'import itertools', number=1000000)
0.5577236771712819

Но подождите — это не совсем честное сравнение...

>>> itertools.repeat(0, 10)
repeat(0, 10)  # Это не список!!!

Функция itertools.repeat фактически не создает список, она просто создает объект, который можно использовать для формирования списка, если это необходимо! Давайте попробуем еще раз, преобразовав результат в список:

>>> timeit.timeit('list(itertools.repeat(0, 10))', 'import itertools', number=1000000)
1.7508119747063233

Таким образом, если вам нужен список, используйте [e] * n. Если хотите генерировать элементы "лениво", используйте repeat.

2

Когда вы умножаете список на число, например, используя конструкцию [5] * 4, результатом будет новый список, содержащий четыре ссылки на одно и то же значение,

>>> [5] * 4
[5, 5, 5, 5]

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

Например:

>>> x = [5]
>>> y = [x] * 4
>>> y
[[5], [5], [5], [5]]

Если вы измените вложенный список через одну из ссылок, то изменение отразится на всех.

Вот пример:

>>> y[0][0] = 6
>>> y
[[6], [6], [6], [6]]

В результате изменения первого элемента в первом вложенном списке, все вложенные списки изменились, поскольку все они ссылаются на один и тот же объект. Поэтому будьте внимательны с использованием умножения для списков, содержащих другие списки!

1

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

Умножение списка для немодифицируемых объектов

Для немодифицируемых элементов, таких как None, булевы значения, целые числа, числа с плавающей запятой, строки, кортежи или frozensets, можно сделать это так:

[e] * 4

Например:

>>> [None] * 4
[None, None, None, None]

Обратите внимание, что это обычно используется только для немодифицируемых объектов (строки, кортежи, frozensets и т.д.) в списке, потому что они все указывают на один и тот же объект в памяти.

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

schema = ['string'] * len(columns)

Умножение списка с повторением одного и того же элемента с изменяемым состоянием

Умножение списка дает нам одинаковые элементы снова и снова. Потребность в этом встречается нечасто:

[iter(iterable)] * 4

Это иногда используется для преобразования итератора в список списков:

>>> iterable = range(12)
>>> a_list = [iter(iterable)] * 4
>>> [[next(l) for l in a_list] for i in range(3)]  # неинтересное использование
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]

Мы можем видеть, что a_list содержит один и тот же итератор диапазона четыре раза:

>>> from pprint import pprint
>>> pprint(a_list)
[<range_iterator object at 0x7f9fe3b58420>,
 <range_iterator object at 0x7f9fe3b58420>,
 <range_iterator object at 0x7f9fe3b58420>,
 <range_iterator object at 0x7f9fe3b58420>]

Изменяемые объекты

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

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

list_of_lists = [[] for _ in iterator_of_needed_length]

Знак нижнего подчеркивания просто является временной переменной в этом контексте.

Если у вас есть только число, это будет:

list_of_lists = [[] for _ in range(4)]

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


Предостережения при использовании метода умножения с изменяемыми объектами:

Остерігайтесь делать это с изменяемыми объектами, когда вы меняете один из них, они все меняются, потому что это все один и тот же объект:

foo = [[]] * 4
foo[0].append('x')

Теперь foo вернет:

[['x'], ['x'], ['x'], ['x']]

Однако с немодифицируемыми объектами вы можете сделать это, потому что вы меняете ссылку, а не объект:

>>> l = [0] * 4
>>> l[0] += 1
>>> l
[1, 0, 0, 0]

>>> l = [frozenset()] * 4
>>> l[0] |= set('abc')
>>> l
[frozenset(['a', 'c', 'b']), frozenset([]), frozenset([]), frozenset([])]

Но снова, изменяемые объекты не годятся для этого, потому что операции на месте изменяют объект, а не ссылку:

l = [set()] * 4
>>> l[0] |= set('abc')    
>>> l
[set(['a', 'c', 'b']), set(['a', 'c', 'b']), set(['a', 'c', 'b']), set(['a', 'c', 'b'])]
0

В библиотеке itertools есть функция именно для этого:

import itertools
it = itertools.repeat(e, n)

Конечно, itertools возвращает итератор, а не список. В то время как [e] * n создаёт список, в зависимости от того, что вы планируете делать с этими последовательностями, вариант с itertools может оказаться гораздо более эффективным.

0

Как уже упоминали другие пользователи, использование оператора * для изменяемого объекта дублирует ссылки, и если вы измените один из них, изменятся все. Если вы хотите создать независимые экземпляры изменяемого объекта, то ваше выражение xrange является самым питоническим способом для этого. Если вас беспокоит наличие именованной переменной, которая никогда не используется, вы можете воспользоваться анонимной переменной с символом подчеркивания.

[e for _ in xrange(n)]
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь