Як клонувати або копіювати список?


2544

Які варіанти клонувати чи копіювати список у Python?

Під час використання new_list = my_listбудь-які зміни new_listзмінюються my_listщоразу. Чому це?

Відповіді:


3324

З 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)]

7
Якщо я не помиляюся: newlist = [*mylist]також є можливість в Python 3. newlist = list(mylist)можливо, це більш зрозуміло.
Стефан

9
Ще одна можливість - new_list = old_list * 1
виникає

4
Який із цих методів є дрібною копією, а який із них - глибокою копією?
Eswar

4
@Eswar: всі, крім останнього, роблять неглибоку копію
Фелікс Клінг

3
@Eswar - це неглибока копія.
juanpa.arrivillaga

602

Фелікс вже дав чудову відповідь, але я подумав, що я порівняю швидкість різних методів:

  1. 10,59 сек (105,9US / itn) - copy.deepcopy(old_list)
  2. 10,16 сек (101,6US / itn) - чистий Copy()метод копіювання класів python за допомогою глибокого копіювання
  3. 1.488 sec (14.88us / itn) - чистий Copy()метод python, який не копіює класи (лише дикти / списки / кортежі)
  4. 0,325 сек (3,25US / itn) - for item in old_list: new_list.append(item)
  5. 0,217 сек (2,17US / itn) - [i for i in old_list]( розуміння списку )
  6. 0,186 сек (1,86US / itn) - copy.copy(old_list)
  7. 0,075 сек (0,75US / itn) - list(old_list)
  8. 0,053 сек (0,53 у.о. / іт.) - new_list = []; new_list.extend(old_list)
  9. 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

9
Оскільки ви орієнтируєте, може бути корисним включити опорну точку. Чи є ці цифри ще точними у 2017 році за допомогою Python 3.6 з повністю складеним кодом? Я зазначаю відповідь нижче ( stackoverflow.com/a/17810305/26219 ) вже ставить під сумнів цю відповідь.
Марк Едінгтон

4
використовувати timeitмодуль. Крім того, ви не можете зробити багато висновку з довільних мікро-орієнтирів, як це.
Корі Голдберг

3
Якщо ви хочете включити новий варіант для версії 3.5+, він [*old_list]повинен бути приблизно еквівалентний list(old_list), але оскільки це синтаксис, а не загальні шляхи виклику функції, це дозволить заощадити трохи під час виконання (і на відміну від цього old_list[:], який не вводить конвертувати, [*old_list]працює на будь-якому ітерабельному і створює а list).
ShadowRanger

3
@CoreyGoldberg щодо дещо менш довільного мікро-еталону (використовує timeit, 50 м пробіг замість 100 к) див. Stackoverflow.com/a/43220129/3745896
Річка

1
@ShadowRanger [*old_list]насправді перевершує майже будь-який інший метод. (див. мою відповідь, пов'язану в попередніх коментарях)
Річка

150

Мені сказали, що Python 3.3+ додаєlist.copy() метод, який повинен бути таким же швидким, як і нарізка:

newlist = old_list.copy()


6
Так, і відповідно до docs.python.org/3/library/stdtypes.html#mutable-sequence-types , s.copy()створюється мілка копія s(те саме s[:]).
CyberMew

Насправді здається, що в даний час python3.8, .copy()це трохи швидше, ніж нарізка. Дивіться нижче відповідь @AaronsHall.
loved.by.Ісус

125

Які варіанти клонувати чи копіювати список у Python?

У Python 3 дрібну копію можна зробити за допомогою:

a_copy = a_list.copy()

У Python 2 і 3 ви можете отримати дрібну копію з повним фрагментом оригіналу:

a_copy = a_list[:]

Пояснення

Існує два смислові способи копіювання списку. Неглибока копія створює новий список тих же об'єктів, глибока копія створює новий список, що містить нові еквівалентні об'єкти.

Копія неглибокого списку

Неглибока копія копіює лише сам список, який є контейнером посилань на об'єкти у списку. Якщо об'єкти, що містяться самі, можуть змінюватися, а один змінюється, зміна відображатиметься в обох списках.

Існують різні способи зробити це в Python 2 та 3. Способи Python 2 також працюватимуть у Python 3.

Пітон 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

Пітон 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 кожен раз, коли мій_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))
  1. Це небезпечно, особливо якщо ви оцінюєте щось із джерела, якому ви не довіряєте.
  2. Це не є надійним, якщо субелемент, який ви копіюєте, не має представлення, яке може бути відтворено еквівалентним елементом.
  3. Це також менш ефективно.

У 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

1
Не потрібна глибока копія, якщо список є двовимірним. Якщо це список списків, а у цих списках немає списків всередині них, ви можете використовувати цикл. В даний час я використовую, list_copy=[] for item in list: list_copy.append(copy(item))і це набагато швидше.
Джон Локк

53

Уже є багато відповідей, які розповідають, як зробити належну копію, але жодна з них не говорить про те, чому ваша оригінальна "копія" не вдалася.

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)

Дивіться документацію для отримання додаткової інформації про кутові справи при копіюванні.



34

Почнемо з початку і вивчимо це питання.

Отже, припустимо, у вас є два списки:

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']]

Як бачимо, він не змінив оригінальний вкладений список, він лише змінив скопійований список.



33

Python 3.6

Ось результати синхронізації за допомогою 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))

1
Можна підтвердити ще подібну історію на 3.8 b=[*a]- єдиний очевидний спосіб зробити це;).
SuperShoot

19

Всі інші учасники давали чудові відповіді, які працюють, коли у вас є єдиний вимір (вирівняний) список, однак з методів, згаданих до цього часу, 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 для багатовимірних списків .


5
Це не завжди спрацює, оскільки немає гарантії, що повернута рядок repr()є достатньою для відновлення об'єкта. Також, eval()це останній засіб; див. Евал дійсно небезпечний для ветерана SO Неда Батчельдера для деталей. Тож, коли ви виступаєте за використання, eval()ви дійсно повинні згадати, що це може бути небезпечно.
PM 2Ring

1
Справедлива точка. Хоча я думаю, що Батчелдер в тому, що eval()функція в Python взагалі є ризиком. Справа не стільки в тому, чи використовуєте ви функцію в коді, чи в тому, що це отвір у захисті Python саме по собі. Мій приклад не використовує його з функцією , яка отримує вхідні дані з input(), sys.agrvабо навіть текстового файлу. Це більше за лініями ініціалізації пустого багатовимірного списку один раз, а потім просто способом його копіювання у циклі, а не повторної ініціалізації при кожній ітерації циклу.
AMR

1
Як зазначає @AaronHall, існує ймовірність у використанні значних проблем new_list = eval(repr(old_list)), тому, крім поганої ідеї, це також, можливо, занадто повільно працює.
AMR

12

Мене дивує, що про це ще не говорилося, тож заради повноти ...

Ви можете виконувати розпакування списку за допомогою "оператора 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)

1
Як поводиться цей метод при зміні копій?
not2qubit

2
@ not2qubit Ви маєте на увазі додавання або редагування елементів нового списку. У прикладі old_listі new_listє два різних списки, редагування одного не змінить іншого (якщо ви безпосередньо не змінюєте самі елементи (наприклад, список списку), жоден із цих методів не є копією).
SCB

7

Дуже простий підхід, незалежний від версії 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

7

Зауважте, що є деякі випадки, коли, якщо ви визначили свій власний клас і хочете зберегти атрибути, то вам слід використовувати 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

4
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)

2

Я хотів опублікувати щось трохи інше, ніж деякі інші відповіді. Незважаючи на те, що це, швидше за все, не самий зрозумілий або найшвидший варіант, він дає деякий вигляд зсередини того, як працює глибока копія, а також є ще одним альтернативним варіантом глибокого копіювання. Не дуже важливо, чи є у моїй функції помилки, оскільки суть цього полягає в тому, щоб показати спосіб копіювання об'єктів, як відповіді на запитання, а також використовувати це як точку, щоб пояснити, як глибока копія працює в основі.

В основі будь-якої функції глибокої копії лежить спосіб зробити дрібну копію. Як? Простий. Будь-яка функція глибокого копіювання лише копіює контейнери незмінних об'єктів. Під час глибокого копіювання вкладеного списку ви дублюєте лише зовнішні списки, а не об'єкти, що змінюються, всередині списків. Ви лише копіюєте контейнери. Те саме працює і для занять. Коли ви глибоко копіюєте клас, ви глибоко копіюєте всі його змінні атрибути. Так як? Чому ви повинні лише скопіювати контейнери, такі як списки, дикти, кортежі, ітери, класи та екземпляри класів?

Це просто. Об'єкт, що змінюється, насправді не можна дублювати. Його ніколи не можна змінити, тому це лише одне значення. Це означає, що вам ніколи не доведеться дублювати рядки, числа, боли або будь-які з них. Але як би ти дублював контейнери? Простий. Ви просто ініціалізуєте новий контейнер з усіма значеннями. Глибока копія спирається на рекурсію. Він копіює всі контейнери, навіть ті, що знаходяться всередині них, поки не залишиться жоден контейнер. Контейнер - це незмінний предмет.

Коли ви знаєте про це, повністю дублювати об’єкт без будь-яких посилань досить просто. Ось функція для глибокого копіювання основних типів даних (не працюватиме для спеціальних класів, але ви завжди можете це додати)

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 використовує рекурсію для дублювання об'єктів і просто повертає ті самі незмінні об'єкти, що і раніше, оскільки незмінні об'єкти не можуть бути дублюються. Однак він глибококопіює найбільш внутрішні шари змінних предметів, поки не дістанеться до самого зовнішнього змінного шару предмета.


2

Незначна практична перспектива виглядати в пам’яті за допомогою 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) 

2

Пам'ятайте про це в Python, коли ви робите:

    list1 = ['apples','bananas','pineapples']
    list2 = list1

List2 не зберігає фактичний список, а посилання на list1. Тож коли ви робите що-небудь для того, щоб список1, також список2 змінюється. використовуйте модуль копіювання (не за замовчуванням, завантажуйте на pip), щоб зробити оригінальну копію списку ( copy.copy()для простих списків, copy.deepcopy()для вкладених). Це робить копію, яка не змінюється з першим списком.


0

Параметр глибокого копіювання - єдиний метод, який працює для мене:

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]]]
-----------------------------
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.