Какие есть варианты для клонирования или копирования списка в Python?
При использовании new_list = my_list
любые new_list
изменения my_list
вносятся каждый раз. Почему это?
С new_list = my_list
, у вас на самом деле нет двух списков. Присвоение просто копирует ссылку на список, а не на фактический список, поэтому new_list
иmy_list
ссылаются на тот же список после назначения.
Чтобы фактически скопировать список, у вас есть различные возможности:
Вы можете использовать встроенный list.copy()
метод (доступен начиная с Python 3.3):
new_list = old_list.copy()
Вы можете нарезать это:
new_list = old_list[:]
Мнение Алекса Мартелли (по крайней мере, в 2007 году ) о том, что это странный синтаксис и не имеет смысла использовать его когда-либо . ;) (По его мнению, следующий более читабелен).
Вы можете использовать встроенную list()
функцию:
new_list = list(old_list)
Вы можете использовать общие copy.copy()
:
import copy
new_list = copy.copy(old_list)
Это немного медленнее, чем list()
потому, что old_list
сначала нужно выяснить тип данных .
Если список содержит объекты и вы хотите скопировать их, используйте generic copy.deepcopy()
:
import copy
new_list = copy.deepcopy(old_list)
Очевидно, самый медленный и самый требующий памяти метод, но иногда неизбежный.
Пример:
import copy
class Foo(object):
def __init__(self, val):
self.val = val
def __repr__(self):
return 'Foo({!r})'.format(self.val)
foo = Foo(1)
a = ['foo', foo]
b = a.copy()
c = a[:]
d = list(a)
e = copy.copy(a)
f = copy.deepcopy(a)
# edit orignal list and instance
a.append('baz')
foo.val = 5
print('original: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r'
% (a, b, c, d, e, f))
Результат:
original: ['foo', Foo(5), 'baz']
list.copy(): ['foo', Foo(5)]
slice: ['foo', Foo(5)]
list(): ['foo', Foo(5)]
copy: ['foo', Foo(5)]
deepcopy: ['foo', Foo(1)]
newlist = [*mylist]
тоже есть возможность в Python 3.newlist = list(mylist)
Может быть, это более понятно.Феликс уже дал отличный ответ, но я подумал, что я сделаю сравнение скорости различных методов:
copy.deepcopy(old_list)
Copy()
метод копирующий классы с глубокой копиейCopy()
метод не копирующий классы (только dicts / lists / tuples)for item in old_list: new_list.append(item)
[i for i in old_list]
( понимание списка )copy.copy(old_list)
list(old_list)
new_list = []; new_list.extend(old_list)
old_list[:]
( нарезка списка )Так что самым быстрым является нарезка списка. Но следует помнить , что
copy.copy()
,list[:]
иlist(list)
, в отличиеcopy.deepcopy()
и версия питона не копировать любые списки, словари и экземпляры классов в списке, так что если оригиналы изменятся, они будут меняться в скопированной списке тоже , и наоборот.(Вот сценарий, если кто-то заинтересован или хочет поднять какие-либо вопросы :)
источник
timeit
модуль. Кроме того, вы не можете сделать много выводов из произвольных микро тестов, как это.[*old_list]
должна быть примерно эквивалентнаlist(old_list)
, но, поскольку это синтаксис, а не общие пути вызова функций, это сэкономит немного времени выполнения (и в отличие от тогоold_list[:]
, что не приводит к типу convert,[*old_list]
работает на любом итерируемом и выдает аlist
).timeit
50м работает вместо 100k) см stackoverflow.com/a/43220129/3745896[*old_list]
самом деле, кажется, превосходит практически любой другой метод. (см. мой ответ в предыдущих комментариях)Мне сказали, что Python 3.3+ добавляет
list.copy()
метод, который должен быть таким же быстрым, как и нарезка:newlist = old_list.copy()
источник
s.copy()
создает неполную копиюs
(такой же , какs[:]
).python3.8
,.copy()
это немного быстрее , чем нарезка. Смотрите ниже @AaronsHall ответ.В Python 3 поверхностная копия может быть сделана с помощью:
В Python 2 и 3 вы можете получить поверхностную копию с полным фрагментом оригинала:
объяснение
Есть два семантических способа скопировать список. Малая копия создает новый список тех же объектов, а глубокая копия создает новый список, содержащий новые эквивалентные объекты.
Мелкая копия списка
Мелкая копия копирует только сам список, который является контейнером ссылок на объекты в списке. Если содержащиеся в нем объекты являются изменяемыми и один из них изменяется, это изменение будет отражено в обоих списках.
Есть разные способы сделать это в Python 2 и 3. Пути Python 2 также будут работать в Python 3.
Python 2
В Python 2 идиоматический способ сделать поверхностную копию списка - это полный фрагмент оригинала:
Вы также можете сделать то же самое, передав список через конструктор списка,
но использование конструктора менее эффективно:
Python 3
В Python 3 списки получают
list.copy
метод:В Python 3.5:
Создание другого указателя не делает копию
my_list
это просто имя, которое указывает на фактический список в памяти. Когда вы говоритеnew_list = my_list
, что не делаете копию, вы просто добавляете другое имя, которое указывает на этот оригинальный список в памяти. У нас могут быть похожие проблемы, когда мы делаем копии списков.Список - это просто массив указателей на содержимое, поэтому поверхностная копия просто копирует указатели, и поэтому у вас есть два разных списка, но они имеют одинаковое содержимое. Чтобы сделать копии содержимого, вам нужна глубокая копия.
Глубокие копии
Чтобы сделать полную копию списка, в Python 2 или 3 используйте
deepcopy
вcopy
модуле :Чтобы продемонстрировать, как это позволяет нам создавать новые подсписки:
Итак, мы видим, что глубоко скопированный список - это совершенно другой список от оригинала. Вы можете свернуть свою собственную функцию - но не делайте. Скорее всего, вы будете создавать ошибки, которых не было бы, используя функцию Deepcopy стандартной библиотеки.
Не использовать
eval
Вы можете увидеть это как способ глубокой копии, но не делайте этого:
В 64-битном Python 2.7:
на 64-битном Python 3.5:
источник
list_copy=[]
for item in list: list_copy.append(copy(item))
и это намного быстрее.Уже есть много ответов, которые говорят вам, как сделать правильную копию, но ни один из них не говорит, почему ваша оригинальная «копия» не удалась.
Python не хранит значения в переменных; это связывает имена с объектами. Ваше первоначальное задание взяло объект, на который ссылается,
my_list
и связало егоnew_list
также. Независимо от того, какое имя вы используете, по-прежнему существует только один список, поэтому изменения, сделанные при обращении к нему,my_list
будут сохраняться при обращении к нему какnew_list
. Каждый из остальных ответов на этот вопрос дает вам различные способы создания нового объекта для привязкиnew_list
.Каждый элемент списка действует как имя в том смысле, что каждый элемент привязывается не только к объекту. Мелкая копия создает новый список, элементы которого связываются с теми же объектами, что и раньше.
Чтобы продвинуть свой список на один шаг вперед, скопируйте каждый объект, к которому относится ваш список, и привяжите эти копии элементов к новому списку.
Это еще не полная копия, потому что каждый элемент списка может ссылаться на другие объекты, так же как список связан с его элементами. Чтобы рекурсивно скопировать каждый элемент в списке, а затем каждый другой объект, на который ссылается каждый элемент, и т. Д. Выполните глубокое копирование.
См. Документацию для получения дополнительной информации об угловых случаях при копировании.
источник
использование
thing[:]
источник
Давайте начнем с самого начала и исследуем этот вопрос.
Итак, предположим, у вас есть два списка:
И мы должны скопировать оба списка, теперь начиная с первого списка:
Итак, сначала давайте попробуем установить переменную
copy
в наш оригинальный списокlist_1
:Теперь, если вы думаете, скопировать скопированный список_1, то вы не правы.
id
Функция может показать нам , если две переменные могут указывать на тот же объект. Давайте попробуем это:Выход:
Обе переменные являются одним и тем же аргументом. Вы удивлены?
Так как мы знаем, что python ничего не хранит в переменной, переменные просто ссылаются на объект, а объект хранит значение. Здесь объект,
list
но мы создали две ссылки на этот же объект с двумя разными именами переменных. Это означает, что обе переменные указывают на один и тот же объект, только с разными именами.Когда вы делаете
copy=list_1
, это на самом деле делает:Здесь на изображении list_1 и copy два имени переменных, но объект одинаков для обеих переменных, которые
list
Поэтому, если вы попытаетесь изменить скопированный список, он также изменит исходный список, потому что список там только один, вы будете изменять этот список независимо от того, делаете ли вы это из скопированного списка или из исходного списка:
вывод:
Таким образом, он изменил оригинальный список:
Теперь давайте перейдем к питоническому методу копирования списков.
Этот метод исправляет первую проблему, которая у нас была:
Таким образом, мы видим, что оба списка имеют разные идентификаторы, и это означает, что обе переменные указывают на разные объекты. Так что на самом деле здесь происходит:
Теперь давайте попробуем изменить список и посмотрим, сталкиваемся ли мы с предыдущей проблемой:
Выход:
Как видите, он только изменил скопированный список. Это значит, что это сработало.
Как вы думаете, мы закончили? Давайте попробуем скопировать наш вложенный список.
list_2
должен ссылаться на другой объект, который является копиейlist_2
. Давайте проверим:Мы получаем вывод:
Теперь мы можем предположить, что оба списка указывают на разные объекты, поэтому теперь давайте попробуем изменить его и посмотрим, что он дает то, что нам нужно:
Это дает нам вывод:
Это может показаться немного запутанным, потому что работал тот же метод, который мы использовали ранее. Давайте попробуем понять это.
Когда вы делаете:
Вы копируете только внешний список, а не внутренний. Мы можем использовать
id
функцию еще раз, чтобы проверить это.Выход:
Когда мы это делаем
copy_2=list_2[:]
, это происходит:Он создает копию списка, но только внешнюю копию списка, а не копию вложенного списка, вложенный список одинаков для обеих переменных, поэтому, если вы попытаетесь изменить вложенный список, он также изменит исходный список, поскольку объект вложенного списка одинаков для обоих списков.
Каково решение? Решением является
deepcopy
функция.Давайте проверим это:
Оба внешних списка имеют разные идентификаторы, давайте попробуем это во внутренних вложенных списках.
Выход:
Как видите, оба идентификатора различны, что означает, что мы можем предположить, что оба вложенных списка теперь указывают на разные объекты.
Это означает, что когда вы делаете
deep=deepcopy(list_2)
то, что на самом деле происходит:Оба вложенных списка указывают на разные объекты, и теперь у них есть отдельная копия вложенного списка.
Теперь давайте попробуем изменить вложенный список и посмотреть, решил ли он предыдущую проблему:
Это выводит:
Как видите, он не изменил исходный вложенный список, он только изменил скопированный список.
источник
Идиома Python для этого
newList = oldList[:]
источник
Сроки Python 3.6
Вот результаты синхронизации с использованием Python 3.6.8. Имейте в виду, что это время относительно друг друга, а не абсолютное.
Я придерживался только создания мелких копий, а также добавил некоторые новые методы, которые были невозможны в Python2, такие как
list.copy()
(эквивалент фрагмента Python3 ) и две формы распаковки списка (*new_list, = list
иnew_list = [*list]
):Мы видим, что победитель Python2 по-прежнему преуспевает, но не сильно вытесняет Python3
list.copy()
, особенно учитывая его превосходную читаемость.Темная лошадка - это метод распаковки и повторной упаковки (
b = [*a]
), который на ~ 25% быстрее, чем нарезка в сыром виде, и более чем в два раза быстрее, чем другой метод распаковки (*b, = a
).b = a * 1
также на удивление хорошо.Обратите внимание, что эти методы не выводят эквивалентные результаты для любого ввода, кроме списков. Все они работают для срезаемых объектов, некоторые работают для любых итеративных, но
copy.copy()
работают только для более общих объектов Python.Вот код тестирования для заинтересованных сторон ( Шаблон отсюда ):
источник
b=[*a]
- единственный очевидный способ сделать это;).Все остальные участники дали отличные ответы, которые работают, когда у вас есть одноуровневый (выровненный) список, однако методы, упомянутые до сих пор,
copy.deepcopy()
работают только для клонирования / копирования списка и не позволяют указывать на вложенныеlist
объекты, когда вы работа с многомерными, вложенными списками (list of lists). Хотя Феликс Клинг ссылается на это в своем ответе, есть еще немного проблемы, и, возможно, обходной путь, использующий встроенные модули, которые могут оказаться более быстрой альтернативойdeepcopy
.В то время
new_list = old_list[:]
,copy.copy(old_list)'
и для Py3kold_list.copy()
работы одноуровневых списков, они возвращаются к указывая наlist
объектах , вложенных в пределахold_list
иnew_list
, а также изменениях в одном изlist
объектов увековечены в других.Редактировать: новая информация, представленная на свет
Как уже говорили другие, существуют значительные проблемы с производительностью при использовании
copy
модуля иcopy.deepcopy
для многомерных списков .источник
repr()
достаточно для воссоздания объекта. Кроме того,eval()
это инструмент последней инстанции; см. Эвал действительно опасен для SO таким ветераном Недом Батчелдером. Поэтому, когда вы пропагандируете использование,eval()
вам действительно следует упомянуть, что оно может быть опасным.eval()
функции в Python в целом является риском. Дело не в том, используете ли вы функцию в коде, а в том, что это дыра в безопасности Python сама по себе. Мой пример не использует его с функцией , которая получает входные данные изinput()
,sys.agrv
или даже текстового файла. Это больше похоже на инициализацию пустого многомерного списка один раз, а затем просто способ его копирования в цикле вместо повторной инициализации на каждой итерации цикла.new_list = eval(repr(old_list))
, поэтому, помимо плохой идеи, она, вероятно, слишком медленная для работы.Меня удивляет, что это еще не было упомянуто, поэтому ради полноты ...
Вы можете выполнить распаковку списка с помощью "оператора сплат":,
*
который также будет копировать элементы вашего списка.Очевидным недостатком этого метода является то, что он доступен только в Python 3.5+.
Тем не менее, это разумно, чем другие распространенные методы.
источник
old_list
иnew_list
представлены два разных списка, редактирование одного не изменит другого (если только вы не изменили сами элементы (например, список списков), ни один из этих методов не является глубоким копированием).Очень простой подход, независимый от версии Python, отсутствовал в уже предоставленных ответах, которые вы можете использовать большую часть времени (по крайней мере, я делаю):
Однако, если my_list содержит другие контейнеры (например, для вложенных списков), вы должны использовать deepcopy, как другие предложили в ответах выше из библиотеки копирования. Например:
, Бонус : если вы не хотите копировать элементы, используйте (или мелкое копирование):
Давайте поймем разницу между решением № 1 и решением № 2
Как видите, решение № 1 работало идеально, когда мы не использовали вложенные списки. Давайте проверим, что произойдет, когда мы применим решение № 1 к вложенным спискам.
источник
Обратите внимание, что в некоторых случаях, если вы определили свой собственный класс и хотите сохранить атрибуты, вам следует использовать
copy.copy()
илиcopy.deepcopy()
вместо них альтернативы, например, в Python 3:Выходы:
источник
new_list = my_list
Попытайся понять это. Предположим, что my_list находится в куче памяти в местоположении X, т.е. my_list указывает на X. Теперь, присваиваяnew_list = my_list
вам значение New_list, указывающее на X. Это называется мелким копированием.Теперь, если вы назначаете,
new_list = my_list[:]
вы просто копируете каждый объект my_list в new_list. Это известно как Глубокая копия.Другой способ сделать это:
new_list = list(old_list)
import copy new_list = copy.deepcopy(old_list)
источник
Я хотел опубликовать что-то немного другое, чем некоторые другие ответы. Несмотря на то, что это, скорее всего, не самый понятный или самый быстрый вариант, он дает представление о том, как работает глубокое копирование, а также является альтернативным вариантом для глубокого копирования. На самом деле не имеет значения, есть ли в моей функции ошибки, поскольку цель этого в том, чтобы показать способ копировать объекты, такие как ответы на вопросы, но также использовать это как точку, чтобы объяснить, как работает глубокая копия в своей основе.
В основе любой функции глубокого копирования лежит способ создания мелкой копии. Как? Просто. Любая функция глубокого копирования только дублирует контейнеры неизменяемых объектов. Когда вы глубоко копируете вложенный список, вы дублируете только внешние списки, а не изменяемые объекты внутри списков. Вы только дублируете контейнеры. То же самое работает и для классов. Когда вы копируете класс глубоко, вы копируете все его изменяемые атрибуты. Так как? Почему у вас есть только копировать контейнеры, такие как списки, dicts, кортежи, iters, классы и экземпляры классов?
Это просто. Изменчивый объект не может быть дублирован. Его нельзя изменить, поэтому это всего лишь одно значение. Это означает, что вам никогда не придется дублировать строки, числа, bools или любые другие. Но как бы вы продублировали контейнеры? Просто. Вы просто инициализируете новый контейнер со всеми значениями. Deepcopy опирается на рекурсию. Он дублирует все контейнеры, даже те, в которых есть контейнеры, до тех пор, пока не останется ни одного контейнера. Контейнер является неизменным объектом.
Как только вы это знаете, полностью дублировать объект без каких-либо ссылок очень легко. Вот функция для глубокого копирования основных типов данных (не будет работать для пользовательских классов, но вы всегда можете добавить это)
Собственная встроенная глубокая копия Python основана на этом примере. Единственное отличие состоит в том, что он поддерживает другие типы, а также поддерживает пользовательские классы, дублируя атрибуты в новый дублирующий класс, а также блокирует бесконечную рекурсию со ссылкой на объект, который уже просматривается с использованием списка заметок или словаря. И это действительно для создания глубоких копий. По своей сути создание глубокой копии - это просто мелкая копия. Я надеюсь, что этот ответ добавляет что-то к вопросу.
ПРИМЕРЫ
Скажем, у вас есть этот список: [1, 2, 3] . Неизменяемые числа не могут быть дублированы, но другой слой может. Вы можете продублировать его, используя понимание списка: [x для x в [1, 2, 3]
Теперь представьте, что у вас есть этот список: [[1, 2], [3, 4], [5, 6]] . На этот раз вы хотите создать функцию, которая использует рекурсию для глубокого копирования всех слоев списка. Вместо предыдущего понимания списка:
Он использует новый для списков:
И deepcopy_list выглядит так:
Теперь у вас есть функция, которая может копировать любой список strs, bools, floast, ints и даже списки в бесконечное число слоев, используя рекурсию. И вот, у вас это есть, глубокий копирование.
TLDR : Deepcopy использует рекурсию для дублирования объектов и просто возвращает те же неизменяемые объекты, что и раньше, поскольку неизменяемые объекты не могут дублироваться. Однако он копирует самые внутренние слои изменяемых объектов до тех пор, пока не достигнет самого изменяемого слоя объекта.
источник
Небольшая практическая перспектива заглянуть в память через id и gc.
источник
Помните, что в Python, когда вы делаете:
List2 хранит не фактический список, а ссылку на list1. Поэтому, когда вы делаете что-либо для list1, list2 также меняется. используйте модуль копирования (не по умолчанию, скачать на pip), чтобы сделать оригинальную копию списка (
copy.copy()
для простых списков,copy.deepcopy()
для вложенных). Это делает копию, которая не изменяется с первым списком.источник
Опция Deepcopy - единственный метод, который работает для меня:
приводит к выводу:
источник
Это потому, что строка
new_list = my_list
назначает новую ссылку на переменную,my_list
котораяnew_list
похожа наC
код, приведенный ниже,Вы должны использовать модуль копирования, чтобы создать новый список
источник