Список неожиданно изменился после назначения. Как мне клонировать или скопировать его, чтобы предотвратить это?
При использовании 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
сначала нужно узнать тип данных .Если список содержит объекты, и вы также хотите их скопировать, используйте общий 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)]
Феликс уже дал отличный ответ, но я подумал, что проведу сравнение скорости различных методов:
- 10,59 сек (105,9us / itn) - copy.deepcopy(old_list)
- 10,16 сек (101.6us / itn) - чистый
Copy()
метод Python, копирующий классы с deepcopy - 1.488 сек (14.88us / itn) - чистый
Copy()
метод Python, не копирующий классы (только dicts / lists / tuples) - 0,325 с (3,25 мкс / итн) -
for item in old_list: new_list.append(item)
- 0.217 сек (2.17us / itn) -
[i for i in old_list]
( понимание списка ) - 0,186 сек (1,86 мкс / итн) - copy.copy(old_list)
- 0,075 сек (0,75 мкс / итн) -
list(old_list)
- 0,053 с (0,53 мкс / итн) -
new_list = []; new_list.extend(old_list)
- 0,039 сек (0,39us / itn) -
old_list[:]
( нарезка списка )
Так что самый быстрый - это нарезка списка. Но следует помнить , что copy.copy()
, list[:]
и list(list)
, в отличие copy.deepcopy()
и версия питона не копировать любые списки, словари и экземпляры классов в списке, так что если оригиналы изменятся, они будут меняться в скопированной списке тоже , и наоборот.
(Вот сценарий, если кто-то заинтересован или хочет поднять какие-либо вопросы :)
from copy import deepcopy
class old_class:
def __init__(self):
self.blah = 'blah'
class new_class(object):
def __init__(self):
self.blah = 'blah'
dignore = {str: None, unicode: None, int: None, type(None): None}
def Copy(obj, use_deepcopy=True):
t = type(obj)
if t in (list, tuple):
if t == tuple:
# Convert to a list if a tuple to
# allow assigning to when copying
is_tuple = True
obj = list(obj)
else:
# Otherwise just do a quick slice copy
obj = obj[:]
is_tuple = False
# Copy each item recursively
for x in xrange(len(obj)):
if type(obj[x]) in dignore:
continue
obj[x] = Copy(obj[x], use_deepcopy)
if is_tuple:
# Convert back into a tuple again
obj = tuple(obj)
elif t == dict:
# Use the fast shallow dict copy() method and copy any
# values which aren't immutable (like lists, dicts etc)
obj = obj.copy()
for k in obj:
if type(obj[k]) in dignore:
continue
obj[k] = Copy(obj[k], use_deepcopy)
elif t in dignore:
# Numeric or string/unicode?
# It's immutable, so ignore it!
pass
elif use_deepcopy:
obj = deepcopy(obj)
return obj
if __name__ == '__main__':
import copy
from time import time
num_times = 100000
L = [None, 'blah', 1, 543.4532,
['foo'], ('bar',), {'blah': 'blah'},
old_class(), new_class()]
t = time()
for i in xrange(num_times):
Copy(L)
print 'Custom Copy:', time()-t
t = time()
for i in xrange(num_times):
Copy(L, use_deepcopy=False)
print 'Custom Copy Only Copying Lists/Tuples/Dicts (no classes):', time()-t
t = time()
for i in xrange(num_times):
copy.copy(L)
print 'copy.copy:', time()-t
t = time()
for i in xrange(num_times):
copy.deepcopy(L)
print 'copy.deepcopy:', time()-t
t = time()
for i in xrange(num_times):
L[:]
print 'list slicing [:]:', time()-t
t = time()
for i in xrange(num_times):
list(L)
print 'list(L):', time()-t
t = time()
for i in xrange(num_times):
[i for i in L]
print 'list expression(L):', time()-t
t = time()
for i in xrange(num_times):
a = []
a.extend(L)
print 'list extend:', time()-t
t = time()
for i in xrange(num_times):
a = []
for y in L:
a.append(y)
print 'list append:', time()-t
t = time()
for i in xrange(num_times):
a = []
a.extend(i for i in L)
print 'generator expression extend:', time()-t
Мне сказали, что Python 3.3+ добавляетlist.copy() метод, который должен быть таким же быстрым, как нарезка:
newlist = old_list.copy()
Какие есть варианты клонирования или копирования списка в Python?
В Python 3 неглубокую копию можно сделать с помощью:
a_copy = a_list.copy()
В Python 2 и 3 вы можете получить мелкую копию с полным фрагментом оригинала:
a_copy = a_list[:]
Объяснение
Есть два семантических способа скопировать список. Неглубокая копия создает новый список тех же объектов, глубокая копия создает новый список, содержащий новые эквивалентные объекты.
Мелкая копия списка
Неглубокая копия копирует только сам список, который является контейнером ссылок на объекты в списке. Если содержащиеся в себе объекты являются изменяемыми и один из них изменен, изменение будет отражено в обоих списках.
Есть разные способы сделать это в Python 2 и 3. Способы Python 2 также будут работать в Python 3.
Python 2
В Python 2 идиоматический способ создания неглубокой копии списка заключается в использовании полного фрагмента оригинала:
a_copy = a_list[:]
Вы также можете сделать то же самое, передав список через конструктор списка,
a_copy = list(a_list)
но использование конструктора менее эффективно:
>>> timeit
>>> l = range(20)
>>> min(timeit.repeat(lambda: l[:]))
0.30504298210144043
>>> min(timeit.repeat(lambda: list(l)))
0.40698814392089844
Python 3
В Python 3 списки получают list.copy
метод:
a_copy = a_list.copy()
В Python 3.5:
>>> import timeit
>>> l = list(range(20))
>>> min(timeit.repeat(lambda: l[:]))
0.38448613602668047
>>> min(timeit.repeat(lambda: list(l)))
0.6309100328944623
>>> min(timeit.repeat(lambda: l.copy()))
0.38122922903858125
Создание другого указателя никак не сделать копию
Использование new_list = my_list затем изменяет new_list каждый раз, когда my_list изменяется. Почему это?
my_list
это просто имя, которое указывает на фактический список в памяти. Когда вы говорите new_list = my_list
, что не делаете копию, вы просто добавляете другое имя, указывающее на исходный список в памяти. Подобные проблемы могут возникать при создании копий списков.
>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]
Список - это просто массив указателей на содержимое, поэтому неглубокая копия просто копирует указатели, и поэтому у вас есть два разных списка, но они имеют одинаковое содержимое. Чтобы сделать копии содержимого, вам понадобится глубокая копия.
Глубокие копии
Чтобы сделать полную копию списка в Python 2 или 3, используйте deepcopyв copyмодуле :
import copy
a_deep_copy = copy.deepcopy(a_list)
Чтобы продемонстрировать, как это позволяет нам создавать новые подсписки:
>>> import copy
>>> l
[['foo'], [], []]
>>> l_deep_copy = copy.deepcopy(l)
>>> l_deep_copy[0].pop()
'foo'
>>> l_deep_copy
[[], [], []]
>>> l
[['foo'], [], []]
Итак, мы видим, что глубоко скопированный список полностью отличается от исходного. Вы можете свернуть свою собственную функцию, но не делайте этого. Вы, вероятно, создадите ошибки, которых иначе не было бы, используя функцию deepcopy стандартной библиотеки.
Не использовать eval
Вы можете видеть, что это используется как способ глубокого копирования, но не делайте этого:
problematic_deep_copy = eval(repr(a_list))
- Это опасно, особенно если вы оцениваете что-то из источника, которому не доверяете.
- Это ненадежно, если копируемый подэлемент не имеет представления, которое может быть оценено для воспроизведения эквивалентного элемента.
- К тому же он менее производительный.
В 64-битном Python 2.7:
>>> import timeit
>>> import copy
>>> l = range(10)
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
27.55826997756958
>>> min(timeit.repeat(lambda: eval(repr(l))))
29.04534101486206
на 64-битном Python 3.5:
>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644
Уже есть много ответов, которые говорят вам, как сделать правильную копию, но ни один из них не говорит, почему ваша оригинальная «копия» не удалась.
Python не хранит значения в переменных; он связывает имена с объектами. В вашем исходном задании объект, на который ссылается, также был my_list
привязан к нему new_list
. Независимо от того, какое имя вы используете, по-прежнему существует только один список, поэтому изменения, сделанные при обращении к нему как my_list
, сохранятся при обращении к нему как new_list
. Каждый из других ответов на этот вопрос дает вам разные способы создания нового объекта для привязки new_list
.
Каждый элемент списка действует как имя, поскольку каждый элемент неэксклюзивно привязывается к объекту. Неглубокая копия создает новый список, элементы которого привязываются к тем же объектам, что и раньше.
new_list = list(my_list) # or my_list[:], but I prefer this syntax
# is simply a shorter way of:
new_list = [element for element in my_list]
Чтобы сделать копию списка на один шаг дальше, скопируйте каждый объект, на который ссылается ваш список, и привяжите эти копии элементов к новому списку.
import copy
# each element must have __copy__ defined for this...
new_list = [copy.copy(element) for element in my_list]
Это еще не полная копия, потому что каждый элемент списка может ссылаться на другие объекты, точно так же, как список привязан к своим элементам. Чтобы рекурсивно копировать каждый элемент в списке, а затем каждый другой объект, на который ссылается каждый элемент, и так далее: выполните глубокую копию.
import copy
# each element must have __deepcopy__ defined for this...
new_list = copy.deepcopy(my_list)
См. Документацию для получения дополнительной информации о угловых случаях при копировании.
Давайте начнем с самого начала и исследуем этот вопрос.
Итак, предположим, у вас есть два списка:
list_1=['01','98']
list_2=[['01','98']]
И мы должны скопировать оба списка, начиная с первого списка:
Итак, сначала давайте попробуем установить переменную copy
в наш исходный список list_1
:
copy=list_1
Теперь, если вы думаете, что копия скопировала список_1, то вы ошибаетесь. id
Функция может показать нам , если две переменные могут указывать на тот же объект. Попробуем это:
print(id(copy))
print(id(list_1))
Результат:
4329485320
4329485320
Обе переменные являются одним и тем же аргументом. Вы удивлены?
Итак, как мы знаем, python ничего не хранит в переменной, переменные просто ссылаются на объект, а объект хранит значение. Здесь объект - это, list
но мы создали две ссылки на этот же объект с двумя разными именами переменных. Это означает, что обе переменные указывают на один и тот же объект, но с разными именами.
Когда вы это делаете copy=list_1
, на самом деле происходит:
Здесь, в изображении list_1 и copy - два имени переменных, но объект одинаковый для обеих переменных, list
Поэтому, если вы попытаетесь изменить скопированный список, он также изменит исходный список, потому что список там только один, вы измените этот список независимо от того, делаете ли вы из скопированного списка или из исходного списка:
copy[0]="modify"
print(copy)
print(list_1)
выход:
['modify', '98']
['modify', '98']
Итак, он изменил исходный список:
Теперь перейдем к питоническому методу копирования списков.
copy_1=list_1[:]
Этот метод устраняет первую возникшую проблему:
print(id(copy_1))
print(id(list_1))
4338792136
4338791432
Итак, мы видим, что оба списка имеют разные идентификаторы, а это означает, что обе переменные указывают на разные объекты. Итак, что на самом деле происходит:
Теперь давайте попробуем изменить список и посмотрим, столкнемся ли мы с предыдущей проблемой:
copy_1[0]="modify"
print(list_1)
print(copy_1)
Результат:
['01', '98']
['modify', '98']
Как видите, он изменил только скопированный список. Значит, сработало.
Думаешь, мы закончили? Нет. Попробуем скопировать наш вложенный список.
copy_2=list_2[:]
list_2
должен ссылаться на другой объект, который является копией list_2
. Давайте проверим:
print(id((list_2)),id(copy_2))
We get the output:
4330403592 4330403528
Now we can assume both lists are pointing different object, so now let's try to modify it and let's see it is giving what we want:
copy_2[0][1]="modify"
print(list_2,copy_2)
This gives us the output:
[['01', 'modify']] [['01', 'modify']]
This may seem a little bit confusing, because the same method we previously used worked. Let's try to understand this.
When you do:
copy_2=list_2[:]
You're only copying the outer list, not the inside list. We can use the id
function once again to check this.
print(id(copy_2[0]))
print(id(list_2[0]))
The output is:
4329485832
4329485832
When we do copy_2=list_2[:]
, this happens:
It creates the copy of list but only outer list copy, not the nested list copy, nested list is same for both variable, so if you try to modify the nested list then it will modify the original list too as the nested list object is same for both lists.
What is the solution? The solution is the deepcopy
function.
from copy import deepcopy
deep=deepcopy(list_2)
Let's check this:
print(id((list_2)),id(deep))
4322146056 4322148040
Both outer lists have different IDs, let's try this on the inner nested lists.
print(id(deep[0]))
print(id(list_2[0]))
The output is:
4322145992
4322145800
As you can see both IDs are different, meaning we can assume that both nested lists are pointing different object now.
This means when you do deep=deepcopy(list_2)
what actually happens:
Both nested lists are pointing different object and they have separate copy of nested list now.
Now let's try to modify the nested list and see if it solved the previous issue or not:
deep[0][1]="modify"
print(list_2,deep)
It outputs:
[['01', '98']] [['01', 'modify']]
As you can see, it didn't modify the original nested list, it only modified the copied list.
Use thing[:]
>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>>
Python 3.6 Timings
Here are the timing results using Python 3.6.8. Keep in mind these times are relative to one another, not absolute.
I stuck to only doing shallow copies, and also added some new methods that weren't possible in Python2, such as list.copy()
(the Python3 slice equivalent) and two forms of list unpacking (*new_list, = list
and new_list = [*list]
):
METHOD TIME TAKEN
b = [*a] 2.75180600000021
b = a * 1 3.50215399999990
b = a[:] 3.78278899999986 # Python2 winner (see above)
b = a.copy() 4.20556500000020 # Python3 "slice equivalent" (see above)
b = []; b.extend(a) 4.68069800000012
b = a[0:len(a)] 6.84498999999959
*b, = a 7.54031799999984
b = list(a) 7.75815899999997
b = [i for i in a] 18.4886440000000
b = copy.copy(a) 18.8254879999999
b = []
for item in a:
b.append(item) 35.4729199999997
We can see the Python2 winner still does well, but doesn't edge out Python3 list.copy()
by much, especially considering the superior readability of the latter.
The dark horse is the unpacking and repacking method (b = [*a]
), which is ~25% faster than raw slicing, and more than twice as fast as the other unpacking method (*b, = a
).
b = a * 1
also does surprisingly well.
Note that these methods do not output equivalent results for any input other than lists. They all work for sliceable objects, a few work for any iterable, but only copy.copy()
works for more general Python objects.
Here is the testing code for interested parties (Template from here):
import timeit
COUNT = 50000000
print("Array duplicating. Tests run", COUNT, "times")
setup = 'a = [0,1,2,3,4,5,6,7,8,9]; import copy'
print("b = list(a)\t\t", timeit.timeit(stmt='b = list(a)', setup=setup, number=COUNT))
print("b = copy.copy(a)\t", timeit.timeit(stmt='b = copy.copy(a)', setup=setup, number=COUNT))
print("b = a.copy()\t\t", timeit.timeit(stmt='b = a.copy()', setup=setup, number=COUNT))
print("b = a[:]\t\t", timeit.timeit(stmt='b = a[:]', setup=setup, number=COUNT))
print("b = a[0:len(a)]\t\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t\t\t", timeit.timeit(stmt='*b, = a', setup=setup, number=COUNT))
print("b = []; b.extend(a)\t", timeit.timeit(stmt='b = []; b.extend(a)', setup=setup, number=COUNT))
print("b = []; for item in a: b.append(item)\t", timeit.timeit(stmt='b = []\nfor item in a: b.append(item)', setup=setup, number=COUNT))
print("b = [i for i in a]\t", timeit.timeit(stmt='b = [i for i in a]', setup=setup, number=COUNT))
print("b = [*a]\t\t", timeit.timeit(stmt='b = [*a]', setup=setup, number=COUNT))
print("b = a * 1\t\t", timeit.timeit(stmt='b = a * 1', setup=setup, number=COUNT))
Python's idiom for doing this is newList = oldList[:]
All of the other contributors gave great answers, which work when you have a single dimension (leveled) list, however of the methods mentioned so far, only copy.deepcopy()
works to clone/copy a list and not have it point to the nested list
objects when you are working with multidimensional, nested lists (list of lists). While Felix Kling refers to it in his answer, there is a little bit more to the issue and possibly a workaround using built-ins that might prove a faster alternative to deepcopy
.
While new_list = old_list[:]
, copy.copy(old_list)'
and for Py3k old_list.copy()
work for single-leveled lists, they revert to pointing at the list
objects nested within the old_list
and the new_list
, and changes to one of the list
objects are perpetuated in the other.
Edit: New information brought to light
As was pointed out by both Aaron Hall and PM 2Ring using
eval()
is not only a bad idea, it is also much slower thancopy.deepcopy()
.This means that for multidimensional lists, the only option is
copy.deepcopy()
. With that being said, it really isn't an option as the performance goes way south when you try to use it on a moderately sized multidimensional array. I tried totimeit
using a 42x42 array, not unheard of or even that large for bioinformatics applications, and I gave up on waiting for a response and just started typing my edit to this post.It would seem that the only real option then is to initialize multiple lists and work on them independently. If anyone has any other suggestions, for how to handle multidimensional list copying, it would be appreciated.
As others have stated, there are significant performance issues using the copy
module and copy.deepcopy
for multidimensional lists.
It surprises me that this hasn't been mentioned yet, so for the sake of completeness...
You can perform list unpacking with the "splat operator": *
, which will also copy elements of your list.
old_list = [1, 2, 3]
new_list = [*old_list]
new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]
The obvious downside to this method is that it is only available in Python 3.5+.
Timing wise though, this appears to perform better than other common methods.
x = [random.random() for _ in range(1000)]
%timeit a = list(x)
%timeit a = x.copy()
%timeit a = x[:]
%timeit a = [*x]
#: 2.47 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.47 µs ± 54.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.39 µs ± 58.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.22 µs ± 43.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
A very simple approach independent of python version was missing in already given answers which you can use most of the time (at least I do):
new_list = my_list * 1 #Solution 1 when you are not using nested lists
However, If my_list contains other containers (for eg. nested lists) you must use deepcopy as others suggested in the answers above from the copy library. For example:
import copy
new_list = copy.deepcopy(my_list) #Solution 2 when you are using nested lists
.Bonus: If you don't want to copy elements use (aka shallow copy):
new_list = my_list[:]
Let's understand difference between Solution#1 and Solution #2
>>> a = range(5)
>>> b = a*1
>>> a,b
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
>>> a[2] = 55
>>> a,b
([0, 1, 55, 3, 4], [0, 1, 2, 3, 4])
As you can see Solution #1 worked perfectly when we were not using the nested lists. Let's check what will happen when we apply solution #1 to nested lists.
>>> from copy import deepcopy
>>> a = [range(i,i+4) for i in range(3)]
>>> a
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> b = a*1
>>> c = deepcopy(a)
>>> for i in (a, b, c): print i
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> a[2].append('99')
>>> for i in (a, b, c): print i
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]] #Solution#1 didn't work in nested list
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]] #Solution #2 - DeepCopy worked in nested list
Note that there are some cases where if you have defined your own custom class and you want to keep the attributes then you should use copy.copy()
or copy.deepcopy()
rather than the alternatives, for example in Python 3:
import copy
class MyList(list):
pass
lst = MyList([1,2,3])
lst.name = 'custom list'
d = {
'original': lst,
'slicecopy' : lst[:],
'lstcopy' : lst.copy(),
'copycopy': copy.copy(lst),
'deepcopy': copy.deepcopy(lst)
}
for k,v in d.items():
print('lst: {}'.format(k), end=', ')
try:
name = v.name
except AttributeError:
name = 'NA'
print('name: {}'.format(name))
Outputs:
lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list
new_list = my_list[:]
new_list = my_list
Try to understand this. Let's say that my_list is in the heap memory at location X i.e. my_list is pointing to the X. Now by assigning new_list = my_list
you're Letting new_list pointing to the X. This is known as shallow Copy.
Now if you assign new_list = my_list[:]
You're simply copying each object of my_list to new_list. This is known as Deep copy.
The Other way you can do this are :
new_list = list(old_list)
import copy new_list = copy.deepcopy(old_list)
I wanted to post something a bit different then some of the other answers. Even though this is most likely not the most understandable, or fastest option, it provides a bit of an inside view of how deep copy works, as well as being another alternative option for deep copying. It doesn't really matter if my function has bugs, since the point of this is to show a way to copy objects like the question answers, but also to use this as a point to explain how deepcopy works at its core.
At the core of any deep copy function is way to make a shallow copy. How? Simple. Any deep copy function only duplicates the containers of immutable objects. When you deepcopy a nested list, you are only duplicating the outer lists, not the mutable objects inside of the lists. You are only duplicating the containers. The same works for classes, too. When you deepcopy a class, you deepcopy all of its mutable attributes. So, how? How come you only have to copy the containers, like lists, dicts, tuples, iters, classes, and class instances?
It's simple. A mutable object can't really be duplicated. It can never be changed, so it is only a single value. That means you never have to duplicate strings, numbers, bools, or any of those. But how would you duplicate the containers? Simple. You make just initialize a new container with all of the values. Deepcopy relies on recursion. It duplicates all the containers, even ones with containers inside of them, until no containers are left. A container is an immutable object.
Once you know that, completely duplicating an object without any references is pretty easy. Here's a function for deepcopying basic data-types (wouldn't work for custom classes but you could always add that)
def deepcopy(x):
immutables = (str, int, bool, float)
mutables = (list, dict, tuple)
if isinstance(x, immutables):
return x
elif isinstance(x, mutables):
if isinstance(x, tuple):
return tuple(deepcopy(list(x)))
elif isinstance(x, list):
return [deepcopy(y) for y in x]
elif isinstance(x, dict):
values = [deepcopy(y) for y in list(x.values())]
keys = list(x.keys())
return dict(zip(keys, values))
Python's own built-in deepcopy is based around that example. The only difference is it supports other types, and also supports user-classes by duplicating the attributes into a new duplicate class, and also blocks infinite-recursion with a reference to an object it's already seen using a memo list or dictionary. And that's really it for making deep copies. At its core, making a deep copy is just making shallow copies. I hope this answer adds something to the question.
EXAMPLES
Say you have this list: [1, 2, 3]. The immutable numbers cannot be duplicated, but the other layer can. You can duplicate it using a list comprehension: [x for x in [1, 2, 3]
Now, imagine you have this list: [[1, 2], [3, 4], [5, 6]]. This time, you want to make a function, which uses recursion to deep copy all layers of the list. Instead of the previous list comprehension:
[x for x in _list]
It uses a new one for lists:
[deepcopy_list(x) for x in _list]
And deepcopy_list looks like this:
def deepcopy_list(x):
if isinstance(x, (str, bool, float, int)):
return x
else:
return [deepcopy_list(y) for y in x]
Then now you have a function which can deepcopy any list of strs, bools, floast, ints and even lists to infinitely many layers using recursion. And there you have it, deepcopying.
TLDR: Deepcopy uses recursion to duplicate objects, and merely returns the same immutable objects as before, as immutable objects cannot be duplicated. However, it deepcopies the most inner layers of mutable objects until it reaches the outermost mutable layer of an object.
A slight practical perspective to look into memory through id and gc.
>>> b = a = ['hell', 'word']
>>> c = ['hell', 'word']
>>> id(a), id(b), id(c)
(4424020872, 4424020872, 4423979272)
| |
-----------
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # all referring to same 'hell'
| | |
-----------------------
>>> id(a[0][0]), id(b[0][0]), id(c[0][0])
(4422785208, 4422785208, 4422785208) # all referring to same 'h'
| | |
-----------------------
>>> a[0] += 'o'
>>> a,b,c
(['hello', 'word'], ['hello', 'word'], ['hell', 'word']) # b changed too
>>> id(a[0]), id(b[0]), id(c[0])
(4424018384, 4424018384, 4424018328) # augmented assignment changed a[0],b[0]
| |
-----------
>>> b = a = ['hell', 'word']
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # the same hell
| | |
-----------------------
>>> import gc
>>> gc.get_referrers(a[0])
[['hell', 'word'], ['hell', 'word']] # one copy belong to a,b, the another for c
>>> gc.get_referrers(('hell'))
[['hell', 'word'], ['hell', 'word'], ('hell', None)] # ('hello', None)
Remember that in Python when you do:
list1 = ['apples','bananas','pineapples']
list2 = list1
List2 isn't storing the actual list, but a reference to list1. So when you do anything to list1, list2 changes as well. use the copy module (not default, download on pip) to make an original copy of the list(copy.copy()
for simple lists, copy.deepcopy()
for nested ones). This makes a copy that doesn't change with the first list.
This is because, the line new_list = my_list
assigns a new reference to the variable my_list
which is new_list
This is similar to the C
code given below,
int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;
You should use the copy module to create a new list by
import copy
new_list = copy.deepcopy(my_list)
The deepcopy option is the only method that works for me:
from copy import deepcopy
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = deepcopy(a)
b[0][1]=[3]
print('Deep:')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = a*1
b[0][1]=[3]
print('*1:')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = a[:]
b[0][1]=[3]
print('Vector copy:')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = list(a)
b[0][1]=[3]
print('List copy:')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = a.copy()
b[0][1]=[3]
print('.copy():')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = a
b[0][1]=[3]
print('Shallow:')
print(a)
print(b)
print('-----------------------------')
leads to output of:
Deep:
[[[1, 2], [1, 2], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
*1:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Vector copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
List copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
.copy():
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Shallow:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------