Создать список из одного элемента, повторенного N раз
Я хочу создать серию списков, каждый раз разной длины. Каждый список должен содержать один и тот же элемент <code>e</code>
, повторенный <code>n</code>
раз (где <code>n</code>
— это длина списка).
Как мне создать эти списки, не используя списковое включение <code>[e for number in range(n)]</code>
для каждого списка?
5 ответ(ов)
Вы также можете записать:
[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
.
Когда вы умножаете список на число, например, используя конструкцию [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]]
В результате изменения первого элемента в первом вложенном списке, все вложенные списки изменились, поскольку все они ссылаются на один и тот же объект. Поэтому будьте внимательны с использованием умножения для списков, содержащих другие списки!
В зависимости от вашего случая использования, вы можете применять разные методы с различной семантикой.
Умножение списка для немодифицируемых объектов
Для немодифицируемых элементов, таких как 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'])]
В библиотеке itertools есть функция именно для этого:
import itertools
it = itertools.repeat(e, n)
Конечно, itertools
возвращает итератор, а не список. В то время как [e] * n
создаёт список, в зависимости от того, что вы планируете делать с этими последовательностями, вариант с itertools
может оказаться гораздо более эффективным.
Как уже упоминали другие пользователи, использование оператора *
для изменяемого объекта дублирует ссылки, и если вы измените один из них, изменятся все. Если вы хотите создать независимые экземпляры изменяемого объекта, то ваше выражение xrange
является самым питоническим способом для этого. Если вас беспокоит наличие именованной переменной, которая никогда не используется, вы можете воспользоваться анонимной переменной с символом подчеркивания.
[e for _ in xrange(n)]
Как получить последний элемент списка?
Как клонировать список, чтобы он не изменялся неожиданно после присваивания?
Самый быстрый способ проверить наличие значения в списке
Сравнение: генераторы списков против lambda + filter
Как отсортировать список/кортеж списков/кортежей по элементу на заданном индексе