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


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

Використовуючи new_list = my_list потім модифікує new_list кожного разу my_list зміни
Чому це?


1698
2018-04-10 08:49


походження




Відповіді:


З new_list = my_list, у вас фактично немає двох списків. Завдання просто копіює посилання на список, а не на фактичний список, тому обидва new_list і my_list зверніться до того ж списку після призначення.

Щоб фактично копіювати список, у вас є різні можливості:

  • Ви можете використовувати вбудований list.copy() метод (доступний з пітона 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 str(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\n list.copy(): %r\n slice: %r\n list(): %r\n copy: %r\n deepcopy: %r'
      % (a, b, c, d, e, f))

Результат:

original: ['foo', 5, 'baz']
list.copy(): ['foo', 5]
slice: ['foo', 5]
list(): ['foo', 5]
copy: ['foo', 5]
deepcopy: ['foo', 1]

2326
2018-04-10 08:55



@FelixKling: чи має сенс редагувати цю відповідь, щоб згадати list.copy метод (доступний з Python 3.3)? Якщо вам не потрібна сумісність з Python 2, це дійсно є Очевидним способом зробити це. - Mark Dickinson
@FelixKling я 100% згоден. Для відповіді на важливе питання Python, цей трохи розкиданий і застарілий. - Jiminion
Якщо я не помиляюся: newlist = [*mylist] також є можливість в Python 3. newlist = list(mylist) може бути більш зрозумілим, хоча. - Stéphane
інша possiblity є new_list = old_list * 1 - aris


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

  1. 10,59 с (105,9 у.о. / ітн) - copy.deepcopy(old_list)
  2. 10.16 с (101.6us / itn) - чистий пітон Copy() метод копіювання класів з глибоким копією
  3. 1.488 с (14.88us / itn) - чистий пітон Copy() метод не копіювання класів (тільки dicts / lists / tuples)
  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] (a усвідомлення списку)
  6. 0.186 с (1.86US / ITN) - copy.copy(old_list)
  7. 0.075 с (0.75us / itn) - list(old_list)
  8. 0,053 сек (0,53US / ітн) - new_list = []; new_list.extend(old_list)
  9. 0,039 с (0,39 в / ітн) - old_list[:] (перелічити нарізання)

Тож найшвидший - це набір сторінок. Але будьте обережні copy.copy(), list[:] і list(list), на відміну від copy.deepcopy() і версія python не копіює будь-які списки, словники та екземпляри класу у списку, тому, якщо оригінали змінюються, вони також зміняться в копійному списку і навпаки.

(Ось скрипт, якщо хтось зацікавлений або хоче виникнути проблем :)

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

РЕДАГУВАТИ: Додано нові стилі, класи старого стилю та dicted до тестів, і зробив версію python набагато швидше, і додав ще кілька методів, включаючи вирази списку та extend().


449
2018-04-10 10:16



Оскільки ви використовуєте порівняльний аналіз, може бути корисно включити орієнтир. Чи ці цифри досі точні у 2017 році, використовуючи Python 3.6 з повністю скомпільованим кодом? Я помічаю відповідь нижче (stackoverflow.com/a/17810305/26219) вже задає цю відповідь. - Mark Edington
використовувати timeit модуль Крім того, ви не можете багато чого зробити з довільних мікрорівнях, як це. - Corey Goldberg


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

newlist = old_list.copy()


116
2017-07-23 12:32





Які варіанти клонування або копіювання списку в 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'], [], []]

І ми бачимо, що глибокий скопійований список - це зовсім інший список з оригіналу. Ви можете запустити власну функцію, але ні. Ви, ймовірно, створите помилки, які ви не могли мати, використовуючи функцію глибокої копії стандартної бібліотеки.

Не використовуйте 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

88
2017-10-25 12:13





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

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)

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


42
2017-11-23 16:45





new_list = list(old_list)


30
2018-04-10 09:03





Використовуйте thing[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>> 

27
2018-04-10 08:53