Відповіді:
З 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)]
Фелікс вже дав чудову відповідь, але я подумав, що я порівняю швидкість різних методів:
copy.deepcopy(old_list)
Copy()
метод копіювання класів python за допомогою глибокого копіюванняCopy()
метод python, який не копіює класи (лише дикти / списки / кортежі)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()
та версія пітона не копіювати будь-які списки, словники і екземпляри класів в списку, так що якщо оригінали зміняться, вони будуть мінятися в скопійованої списку теж , і навпаки.
(Ось сценарій, якщо хтось зацікавлений або хоче порушити якісь питання :)
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
timeit
модуль. Крім того, ви не можете зробити багато висновку з довільних мікро-орієнтирів, як це.
[*old_list]
повинен бути приблизно еквівалентний list(old_list)
, але оскільки це синтаксис, а не загальні шляхи виклику функції, це дозволить заощадити трохи під час виконання (і на відміну від цього old_list[:]
, який не вводить конвертувати, [*old_list]
працює на будь-якому ітерабельному і створює а list
).
timeit
, 50 м пробіг замість 100 к) див. 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?
У Python 3 дрібну копію можна зробити за допомогою:
a_copy = a_list.copy()
У Python 2 і 3 ви можете отримати дрібну копію з повним фрагментом оригіналу:
a_copy = a_list[:]
Існує два смислові способи копіювання списку. Неглибока копія створює новий список тих же об'єктів, глибока копія створює новий список, що містить нові еквівалентні об'єкти.
Неглибока копія копіює лише сам список, який є контейнером посилань на об'єкти у списку. Якщо об'єкти, що містяться самі, можуть змінюватися, а один змінюється, зміна відображатиметься в обох списках.
Існують різні способи зробити це в Python 2 та 3. Способи Python 2 також працюватимуть у Python 3.
У 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 списки отримують 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 кожен раз, коли мій_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'], [], []]
І тому ми бачимо, що глибоко скопійований список - це зовсім інший список від оригіналу. Ви можете виконувати власну функцію - але ні. Ви, ймовірно, створюєте помилки, яких інакше не було б, використовуючи стандартну функцію глибокого копіювання бібліотеки.
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
list_copy=[]
for item in list: list_copy.append(copy(item))
і це набагато швидше.
Уже є багато відповідей, які розповідають, як зробити належну копію, але жодна з них не говорить про те, чому ваша оригінальна "копія" не вдалася.
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)
Дивіться документацію для отримання додаткової інформації про кутові справи при копіюванні.
Використовуйте thing[:]
>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>>
Почнемо з початку і вивчимо це питання.
Отже, припустимо, у вас є два списки:
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
, це насправді робить:
Тут у списку зображень_1 та копії є два імені змінних, але об’єкт однаковий для обох змінних, що є 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))
Ми отримуємо вихід:
4330403592 4330403528
Тепер ми можемо припустити, що обидва списки вказують на різний об’єкт, тому тепер спробуємо змінити його, і давайте подивимось, що він дає те, що ми хочемо:
copy_2[0][1]="modify"
print(list_2,copy_2)
Це дає нам вихід:
[['01', 'modify']] [['01', 'modify']]
Це може здатися трохи заплутаним, оскільки працював той самий метод, який ми використовували раніше. Спробуємо розібратися в цьому.
Коли ви робите:
copy_2=list_2[:]
Ви копіюєте лише зовнішній список, а не внутрішній. Ми можемо використати id
функцію ще раз, щоб перевірити це.
print(id(copy_2[0]))
print(id(list_2[0]))
Вихід:
4329485832
4329485832
Коли ми це робимо copy_2=list_2[:]
, це відбувається:
Він створює копію списку, але тільки зовнішню копію списку, а не вкладену копію списку, вкладений список однаковий для обох змінних, тому, якщо ви спробуєте змінити вкладений список, він також змінить оригінальний список, оскільки об'єкт вкладеного списку однаковий для обох списків.
Яке рішення? Рішення - deepcopy
функція.
from copy import deepcopy
deep=deepcopy(list_2)
Давайте перевіримо це:
print(id((list_2)),id(deep))
4322146056 4322148040
Обидва зовнішні списки мають різні ідентифікатори, давайте спробуємо це у внутрішніх вкладених списках.
print(id(deep[0]))
print(id(list_2[0]))
Вихід:
4322145992
4322145800
Як ви можете бачити, що обидва ідентифікатори різні, це означає, що ми можемо припустити, що обидва вкладені списки вказують на інший об'єкт зараз.
Це означає, що ви робите deep=deepcopy(list_2)
те, що відбувається насправді:
Обидва вкладені списки вказують на інший об'єкт, і зараз вони мають окрему копію вкладеного списку.
Тепер спробуємо змінити вкладений список і подивимось, чи вирішив він попередню проблему чи ні:
deep[0][1]="modify"
print(list_2,deep)
Він виводить:
[['01', '98']] [['01', 'modify']]
Як бачимо, він не змінив оригінальний вкладений список, він лише змінив скопійований список.
Ось результати синхронізації за допомогою Python 3.6.8. Майте на увазі, що ці часи відносно один одного, а не абсолютні.
Я затримався робити лише дрібні копії, а також додав нові методи, які не були можливі в Python2, такі як list.copy()
( еквівалент фрагмента Python3 ) та дві форми розпакування списку ( *new_list, = list
і 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
Ми можемо бачити, що переможець Python2 все ще добре, але Python3 не випереджає list.copy()
багато, особливо враховуючи кращу читанність останнього.
Темний кінь - це метод розпакування та упаковки ( b = [*a]
), який на ~ 25% швидший, ніж сирої нарізки, і більш ніж удвічі швидший, ніж інший метод розпакування ( *b, = a
).
b = a * 1
також дивно добре.
Зауважте, що ці методи не дають еквівалентних результатів для будь-якого введення, крім списків. Усі вони працюють для надрізних об'єктів, кілька - для будь-яких ітерабельних, але вони працюють лише copy.copy()
для більш загальних об'єктів Python.
Ось код тестування для зацікавлених сторін ( шаблон звідси ):
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))
b=[*a]
- єдиний очевидний спосіб зробити це;).
Всі інші учасники давали чудові відповіді, які працюють, коли у вас є єдиний вимір (вирівняний) список, однак з методів, згаданих до цього часу, copy.deepcopy()
працює лише для клонування / копіювання списку і не вказує на вкладені list
об'єкти, коли ви робота з багатовимірними, вкладеними списками (список списків). Хоча Фелікс Клінг посилається на це у своїй відповіді, є дещо більше питання та, можливо, вирішення методів використання вбудованих модулів, які можуть стати більш швидкою альтернативою deepcopy
.
У той час new_list = old_list[:]
, copy.copy(old_list)'
і для old_list.copy()
роботи Py3k для однорівневих списків вони повертаються до вказівки на list
об'єкти, вкладені в old_list
і new_list
, а зміни в одному з list
об'єктів зберігаються в іншому.
Як вказували і Aaron Hall, і PM 2Ring, використовуючи
eval()
це не тільки погана ідея, вона також набагато повільніше, ніжcopy.deepcopy()
.Це означає, що для багатовимірних списків єдиний варіант є
copy.deepcopy()
. Зважаючи на це, це насправді не є варіантом, оскільки при спробі використовувати його на багатовимірному масиві середнього розміру продуктивність працює на південь. Я спробувавtimeit
використовувати масив 42x42, що не є нечуваним або навіть таким великим для програм біоінформатики, і я відмовився чекати відповіді і просто почав вводити свою редакцію в цю публікацію.Здавалося б, єдиний реальний варіант тоді - ініціалізувати декілька списків та працювати над ними самостійно. Якщо у когось є якісь інші пропозиції щодо обробки багатовимірного копіювання списку, було б вдячно.
Як зазначають інші, існують значні проблеми з продуктивністю використання copy
модуля та copy.deepcopy
для багатовимірних списків .
repr()
є достатньою для відновлення об'єкта. Також, eval()
це останній засіб; див. Евал дійсно небезпечний для ветерана SO Неда Батчельдера для деталей. Тож, коли ви виступаєте за використання, eval()
ви дійсно повинні згадати, що це може бути небезпечно.
eval()
функція в Python взагалі є ризиком. Справа не стільки в тому, чи використовуєте ви функцію в коді, чи в тому, що це отвір у захисті Python саме по собі. Мій приклад не використовує його з функцією , яка отримує вхідні дані з input()
, sys.agrv
або навіть текстового файлу. Це більше за лініями ініціалізації пустого багатовимірного списку один раз, а потім просто способом його копіювання у циклі, а не повторної ініціалізації при кожній ітерації циклу.
new_list = eval(repr(old_list))
, тому, крім поганої ідеї, це також, можливо, занадто повільно працює.
Мене дивує, що про це ще не говорилося, тож заради повноти ...
Ви можете виконувати розпакування списку за допомогою "оператора splat":, *
який також буде копіювати елементи вашого списку.
old_list = [1, 2, 3]
new_list = [*old_list]
new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]
Очевидним недоліком цього методу є те, що він доступний лише в Python 3.5+.
Хоча розумні терміни, це, здається, працює краще, ніж інші поширені методи.
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)
old_list
і new_list
є два різних списки, редагування одного не змінить іншого (якщо ви безпосередньо не змінюєте самі елементи (наприклад, список списку), жоден із цих методів не є копією).
Дуже простий підхід, незалежний від версії python, бракував у вже даних відповідях, які ви можете використовувати більшу частину часу (принаймні, я):
new_list = my_list * 1 #Solution 1 when you are not using nested lists
Однак якщо my_list містить інші контейнери (наприклад, вкладені списки), ви повинні використовувати глибоку копію, як інші, запропоновані у відповідях вище з бібліотеки копій. Наприклад:
import copy
new_list = copy.deepcopy(my_list) #Solution 2 when you are using nested lists
. Бонус : Якщо ви не хочете копіювати елементи, використовуйте (також дрібну копію):
new_list = my_list[:]
Розберемо різницю між Рішенням №1 та Рішенням №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])
Як бачимо, рішення №1 прекрасно працювало, коли ми не використовували вкладені списки. Давайте перевіримо, що буде, коли ми застосуємо рішення №1 до вкладених списків.
>>> 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
Зауважте, що є деякі випадки, коли, якщо ви визначили свій власний клас і хочете зберегти атрибути, то вам слід використовувати copy.copy()
або copy.deepcopy()
замість альтернативи, наприклад, в 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))
Виходи:
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
Спробуйте це зрозуміти. Скажімо, my_list знаходиться в пам'яті купи в місці X, тобто my_list вказує на X. Тепер, призначившиnew_list = my_list
ви дозволяєте new_list вказувати на X. Це відоме як неглибока копія.
Тепер якщо ви призначите, new_list = my_list[:]
ви просто копіюєте кожен об’єкт з мого списку до нового списку. Це відоме як глибока копія.
Інший спосіб це зробити:
new_list = list(old_list)
import copy
new_list = copy.deepcopy(old_list)
Я хотів опублікувати щось трохи інше, ніж деякі інші відповіді. Незважаючи на те, що це, швидше за все, не самий зрозумілий або найшвидший варіант, він дає деякий вигляд зсередини того, як працює глибока копія, а також є ще одним альтернативним варіантом глибокого копіювання. Не дуже важливо, чи є у моїй функції помилки, оскільки суть цього полягає в тому, щоб показати спосіб копіювання об'єктів, як відповіді на запитання, а також використовувати це як точку, щоб пояснити, як глибока копія працює в основі.
В основі будь-якої функції глибокої копії лежить спосіб зробити дрібну копію. Як? Простий. Будь-яка функція глибокого копіювання лише копіює контейнери незмінних об'єктів. Під час глибокого копіювання вкладеного списку ви дублюєте лише зовнішні списки, а не об'єкти, що змінюються, всередині списків. Ви лише копіюєте контейнери. Те саме працює і для занять. Коли ви глибоко копіюєте клас, ви глибоко копіюєте всі його змінні атрибути. Так як? Чому ви повинні лише скопіювати контейнери, такі як списки, дикти, кортежі, ітери, класи та екземпляри класів?
Це просто. Об'єкт, що змінюється, насправді не можна дублювати. Його ніколи не можна змінити, тому це лише одне значення. Це означає, що вам ніколи не доведеться дублювати рядки, числа, боли або будь-які з них. Але як би ти дублював контейнери? Простий. Ви просто ініціалізуєте новий контейнер з усіма значеннями. Глибока копія спирається на рекурсію. Він копіює всі контейнери, навіть ті, що знаходяться всередині них, поки не залишиться жоден контейнер. Контейнер - це незмінний предмет.
Коли ви знаєте про це, повністю дублювати об’єкт без будь-яких посилань досить просто. Ось функція для глибокого копіювання основних типів даних (не працюватиме для спеціальних класів, але ви завжди можете це додати)
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. Єдина відмінність полягає в тому, що він підтримує інші типи, а також підтримує користувацькі класи шляхом дублювання атрибутів у новому класі дублікатів, а також блокує нескінченну рекурсію з посиланням на об'єкт, який уже бачили за допомогою списку нагадувань або словника. І це справді все для створення глибоких копій. По суті, зробити глибоку копію - це просто зробити дрібні копії. Я сподіваюся, що ця відповідь щось додає до питання.
ПРИКЛАДИ
Скажіть, у вас є цей список: [1, 2, 3] . Незмінні числа не можна дублювати, але інший шар може. Ви можете скопіювати його за допомогою розуміння списку: [x for x in [1, 2, 3]
А тепер уявіть, що у вас є цей список: [[1, 2], [3, 4], [5, 6]] . Цього разу ви хочете зробити функцію, яка використовує рекурсію для глибокої копіювання всіх шарів списку. Замість попереднього розуміння списку:
[x for x in _list]
Він використовує новий для списків:
[deepcopy_list(x) for x in _list]
І deepcopy_list виглядає так:
def deepcopy_list(x):
if isinstance(x, (str, bool, float, int)):
return x
else:
return [deepcopy_list(y) for y in x]
Тоді тепер у вас є функція, яка може глибоко скопіювати будь-який список strs, bools, floast, ints і навіть списки для нескінченно багатьох шарів за допомогою рекурсії. І ось у вас це є, глибоке копіювання.
TLDR : Deepcopy використовує рекурсію для дублювання об'єктів і просто повертає ті самі незмінні об'єкти, що і раніше, оскільки незмінні об'єкти не можуть бути дублюються. Однак він глибококопіює найбільш внутрішні шари змінних предметів, поки не дістанеться до самого зовнішнього змінного шару предмета.
Незначна практична перспектива виглядати в пам’яті за допомогою id та 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)
Пам'ятайте про це в Python, коли ви робите:
list1 = ['apples','bananas','pineapples']
list2 = list1
List2 не зберігає фактичний список, а посилання на list1. Тож коли ви робите що-небудь для того, щоб список1, також список2 змінюється. використовуйте модуль копіювання (не за замовчуванням, завантажуйте на pip), щоб зробити оригінальну копію списку ( copy.copy()
для простих списків, copy.deepcopy()
для вкладених). Це робить копію, яка не змінюється з першим списком.
Параметр глибокого копіювання - єдиний метод, який працює для мене:
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('-----------------------------')
призводить до виходу:
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]]]
-----------------------------
newlist = [*mylist]
також є можливість в Python 3.newlist = list(mylist)
можливо, це більш зрозуміло.