Неожиданно внесение изменений в список после назначения. Почему это так и как это предотвратить?

avatar
aF.
10 апреля 2010 в 08:49
1893718
22
2900

При использовании new_list = my_list любые изменения в new_list изменяют my_list каждый раз. Почему это так и как я могу клонировать или скопировать список, чтобы предотвратить это?

Источник

Ответы (22)

avatar
Felix Kling
10 апреля 2010 в 08:55
3674

С 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)]
moojen
21 декабря 2020 в 17:27
0

Как правильно указывает @Georgy в ответе ниже, любые изменения значений new_list также изменят значения в my_list. Таким образом, на самом деле метод copy.deepcopy () является единственной реальной копией без ссылки на исходный список и его значения.

Georgy
21 декабря 2020 в 17:33
0

@ Эрри, я думаю, ты ошибся. Я не публиковал здесь ни ответов, ни комментариев :)

moojen
21 декабря 2020 в 17:52
0

Вы правы, он был отредактирован вами, но опубликован @cryo Извините за путаницу!

zzz777
4 мая 2021 в 18:10
0

Какой из них самый быстрый?

Tom
11 мая 2021 в 15:23
0

У меня была такая же проблема со списком json (каждый элемент списка был json), и единственный, который работал, был new_list = copy.deepcopy (old_list); Я пишу это, потому что любой может столкнуться с такой же проблемой. Спасибо!

avatar
fjemi
30 января 2021 в 20:17
2

Используемый метод зависит от содержимого копируемого списка. Если список содержит вложенный dicts, то глубокое копирование - единственный метод, который работает, в противном случае большинство методов, перечисленных в ответах (срез, цикл [для], копирование, расширение, объединение или распаковка) будут работать и выполняться в одно и то же время. (за исключением цикла и глубокой копии, которые были наихудшими). ​​

Скрипт

from random import randint
from time import time
import copy

item_count = 100000

def copy_type(l1: list, l2: list):
  if l1 == l2:
    return 'shallow'
  return 'deep'

def run_time(start, end):
  run = end - start
  return int(run * 1000000)

def list_combine(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [] + l1
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'combine', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_extend(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  l2.extend(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'extend', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_unpack(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [*l1]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'unpack', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_deepcopy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = copy.deepcopy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'deepcopy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_copy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list.copy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'copy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_slice(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = l1[:]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'slice', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_loop(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  for i in range(len(l1)):
    l2.append(l1[i])
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'loop', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_list(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'list()', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

if __name__ == '__main__':
  list_type = [{'list[dict]': {'test': [1, 1]}}, 
          {'list[list]': [1, 1]}]
  store = []
  for data in list_type:
    key = list(data.keys())[0]
    store.append({key: [list_unpack(data[key]), list_extend(data[key]), 
                list_combine(data[key]), list_deepcopy(data[key]), 
                list_copy(data[key]), list_slice(data[key]),           
                list_loop(data[key])]})
  print(store)

Результаты

[{"list[dict]": [
  {"method": "unpack", "copy_type": "shallow", "time_µs": 56149},
  {"method": "extend", "copy_type": "shallow", "time_µs": 52991},
  {"method": "combine", "copy_type": "shallow", "time_µs": 53726},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2702616},
  {"method": "copy", "copy_type": "shallow", "time_µs": 52204},
  {"method": "slice", "copy_type": "shallow", "time_µs": 52223},
  {"method": "loop", "copy_type": "shallow", "time_µs": 836928}]},
{"list[list]": [
  {"method": "unpack", "copy_type": "deep", "time_µs": 52313},
  {"method": "extend", "copy_type": "deep", "time_µs": 52550},
  {"method": "combine", "copy_type": "deep", "time_µs": 53203},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2608560},
  {"method": "copy", "copy_type": "deep", "time_µs": 53210},
  {"method": "slice", "copy_type": "deep", "time_µs": 52937},
  {"method": "loop", "copy_type": "deep", "time_µs": 834774}
]}]
avatar
Laurent Lyaudet
3 июля 2020 в 13:15
0

Есть другой способ скопировать список, который не был указан до сих пор: добавление пустого списка: l2 = l + [].

Я тестировал его на Python 3.8:

l = [1,2,3]
l2 = l + []
print(l,l2)
l[0] = 'a'
print(l,l2)

Это не лучший ответ, но он работает.

avatar
Code Carbonate
3 июля 2020 в 03:38
0

Есть простой способ справиться с этим.

Код:

number=[1,2,3,4,5,6] #Original list
another=[] #another empty list
for a in number: #here I am declaring variable (a) as an item in the list (number)
    another.append(a) #here we are adding the items of list (number) to list (another)
print(another)

Вывод:

>>> [1,2,3,4,5,6]

Надеюсь, это было полезно для вашего запроса.

avatar
Roshin Raphel
4 июня 2020 в 10:40
2

Это потому, что строка new_list = my_list назначает новую ссылку на переменную my_list, которая равна new_list Это похоже на код C, приведенный ниже,

int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;

Вы должны использовать модуль копирования, чтобы создать новый список по

import copy
new_list = copy.deepcopy(my_list)
avatar
shahar_m
11 апреля 2020 в 11:19
2

Параметр 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]]]
-----------------------------
Jean-François Fabre♦
19 ноября 2020 в 13:43
0

Deepcopy следует использовать только тогда, когда это необходимо, и нужно знать, что он на самом деле делает.

avatar
Dr. Hippo
22 февраля 2020 в 12:44
5

Помните, что в Python, когда вы делаете:

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

List2 хранит не фактический список, а ссылку на list1. Поэтому, когда вы делаете что-либо для list1, list2 также изменяется. используйте модуль копирования (не по умолчанию, загрузка через pip), чтобы сделать исходную копию списка (copy.copy() для простых списков, copy.deepcopy() для вложенных). Это создает копию, которая не изменяется с первым списком.

avatar
B.Mr.W.
23 ноября 2019 в 19:01
4

Небольшая практическая перспектива заглянуть в память через 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) 
avatar
Corman
8 сентября 2019 в 02:25
5

Я хотел опубликовать что-то немного отличное от некоторых других ответов. Хотя это, скорее всего, не самый понятный или самый быстрый вариант, он дает некоторое представление о том, как работает глубокое копирование, а также является еще одним альтернативным вариантом для глубокого копирования. На самом деле не имеет значения, есть ли в моей функции ошибки, поскольку цель этого состоит в том, чтобы показать способ копирования объектов, таких как ответы на вопросы, но также использовать это как точку, чтобы объяснить, как deepcopy работает в своей основе.

В основе любой функции глубокого копирования лежит способ создания неглубокой копии. Как? Простой. Любая функция глубокого копирования дублирует только контейнеры неизменяемых объектов. Когда вы глубоко копируете вложенный список, вы дублируете только внешние списки, а не изменяемые объекты внутри списков. Вы только дублируете контейнеры. То же самое и с классами. Когда вы глубоко копируете класс, вы глубоко копируете все его изменяемые атрибуты. Так как? Почему вам нужно скопировать только контейнеры, такие как списки, словари, кортежи, итеры, классы и экземпляры классов?

Все просто. Изменяемый объект не может быть продублирован. Его нельзя изменить, поэтому это только одно значение. Это означает, что вам никогда не придется дублировать строки, числа, логические значения или любые из них. Но как бы вы продублировали контейнеры? Простой. Вы просто инициализируете новый контейнер со всеми значениями. Deepcopy полагается на рекурсию. Он дублирует все контейнеры, даже те, в которых есть контейнеры, до тех пор, пока контейнеров не останется. Контейнер - это неизменяемый объект.

Как только вы это знаете, полностью дублировать объект без каких-либо ссылок довольно просто. Вот функция для глубокого копирования основных типов данных (не будет работать для пользовательских классов, но вы всегда можете добавить это)

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

avatar
Chris_Rands
16 мая 2018 в 14:31
8

Обратите внимание, что в некоторых случаях, если вы определили свой собственный класс и хотите сохранить атрибуты, вам следует использовать 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
avatar
SCB
26 февраля 2018 в 02:33
16

Меня удивляет, что об этом еще не упоминалось, поэтому для полноты картины ...

Вы можете выполнить распаковку списка с помощью «оператора 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)
not2qubit
24 сентября 2018 в 13:35
1

Как этот метод ведет себя при изменении копий?

SCB
25 сентября 2018 в 14:07
2

@ not2qubit означает добавление или редактирование элементов нового списка. В примере old_list и new_list - это два разных списка, редактирование одного не изменит другой (если вы напрямую не изменяете сами элементы (например, список списка), ни один из этих методов не является глубокой копией).

avatar
Aaditya Ura
13 ноября 2017 в 07:04
55

Давайте начнем с самого начала и исследуем этот вопрос.

Предположим, у вас есть два списка:

list_1 = ['01', '98']
list_2 = [['01', '98']]

И мы должны скопировать оба списка, теперь начиная с первого списка:

Итак, сначала давайте попробуем установить переменную copy в наш исходный список, list_1:

copy = list_1

Теперь, если вы думаете, что копия скопировала list_1 , то вы ошибаетесь. Функция id может показать нам, могут ли две переменные указывать на один и тот же объект. Давайте попробуем это:

print(id(copy))
print(id(list_1))

Вывод:

4329485320
4329485320

Обе переменные - это один и тот же аргумент. Вы удивлены?

Итак, как мы знаем, Python ничего не хранит в переменной, переменные просто ссылаются на объект, а объект хранит значение. Здесь объект - list, но мы создали две ссылки на этот же объект с двумя разными именами переменных. Это означает, что обе переменные указывают на один и тот же объект, но с разными именами.

Когда вы выполняете copy = list_1, на самом деле происходит:

Enter image description here

Здесь, на изображении, list_1 и copy - это имена двух переменных, но объект одинаковый для обеих переменных: list.

Итак, если вы попытаетесь изменить скопированный список, он также изменит исходный список, потому что список там только один, вы измените этот список независимо от того, делаете ли вы из скопированного списка или из исходного списка:

copy[0] = "modify"

print(copy)
print(list_1)

Вывод:

['modify', '98']
['modify', '98']

Итак, он изменил исходный список:

Теперь перейдем к методу Pythonic для копирования списков.

copy_1 = list_1[:]

Этот метод устраняет первую возникшую проблему:

print(id(copy_1))
print(id(list_1))

4338792136
4338791432

Итак, мы видим, что оба наших списка имеют разные идентификаторы, и это означает, что обе переменные указывают на разные объекты. Итак, что на самом деле происходит:

Enter image description here

Теперь давайте попробуем изменить список и посмотрим, столкнемся ли мы с предыдущей проблемой:

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[:], происходит следующее:

Enter image description here

Создает копию списка, но только копию внешнего списка, а не копию вложенного списка. Вложенный список одинаков для обеих переменных, поэтому, если вы попытаетесь изменить вложенный список, он также изменит исходный список, поскольку объект вложенного списка одинаков для обоих списков.

Какое решение? Решением является функция 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), что происходит на самом деле:

Enter image description here

Оба вложенных списка указывают на разные объекты, и теперь у них есть отдельная копия вложенного списка.

Теперь давайте попробуем изменить вложенный список и посмотрим, решило ли оно предыдущую проблему или нет:

deep[0][1] = "modify"
print(list_2, deep)

Выводит:

[['01', '98']] [['01', 'modify']]

Как видите, он не изменил исходный вложенный список, а только изменил скопированный список.

avatar
jainashish
1 ноября 2017 в 08:08
11

Очень простой подход, независимый от версии 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

. Бонус : Если вы не хотите копировать элементы, используйте (мелкое копирование AKA):

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
avatar
Ravi Shankar
26 июня 2017 в 21:03
6
new_list = my_list[:]

new_list = my_list

Попытайтесь понять это. Предположим, что my_list находится в памяти кучи в местоположении X, т.е. my_list указывает на X. Теперь, назначив new_list = my_list, вы разрешаете <5556891488557689> new_list указывают на X. Это известно как неглубокая копия .

Теперь, если вы назначаете new_list = my_list[:], вы просто копируете каждый объект из my_list в new_list . Это известно как глубокая копия .

другой способ сделать это:

  • new_list = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)
avatar
River
5 апреля 2017 в 01:01
37

Тайминги Python 3.6

Вот результаты синхронизации с использованием Python 3.6.8. Имейте в виду, что это время относительно друг друга, а не абсолютное.

Я придерживался только мелких копий, а также добавил несколько новых методов, которые были невозможны в Python 2, такие как list.copy() (Python 3 эквивалент фрагмента) и две формы распаковка списка (*new_list, = list и new_list = [*list]): ​​

METHOD                TIME TAKEN
b = [*a]               2.75180600000021
b = a * 1              3.50215399999990
b = a[:]               3.78278899999986  # Python 2 winner (see above)
b = a.copy()           4.20556500000020  # Python 3 "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

Мы видим, что победитель Python 2 по-прежнему преуспевает, но не намного превосходит Python 3 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))

SuperShoot
2 марта 2020 в 00:02
1

Могу подтвердить еще похожую историю на 3.8 b=[*a] - единственный очевидный способ сделать это;).

ekhumoro
21 ноября 2020 в 20:15
1

Некоторые из этих сравнений по времени не имеют особого смысла при копировании таких крошечных списков. Было бы более информативным тестировать с диапазоном длин списков (включая некоторые очень большие).

Peter Mortensen
11 мая 2021 в 21:43
0

Числа времени должны быть округлены до соответствующего количества значащих цифр. 15 значащих цифр не имеют никакого смысла.

River
18 мая 2021 в 00:38
0

По сути, я просто вставил сюда необработанный вывод временного кода. Похоже, ваше недовольство больше связано с тем, как timeit отображает тайминги, которые я мало контролирую.

avatar
AMR
10 июля 2015 в 03:51
22

Все другие участники дали отличные ответы, которые работают, когда у вас есть одномерный (выровненный) список, однако из методов, упомянутых до сих пор, только copy.deepcopy() работает для клонирования / копирования списка и не указывать на вложенные объекты list, когда вы работаете с многомерными вложенными списками (списками списков). Хотя Феликс Клинг упоминает об этом в своем ответе, есть немного больше проблемы и, возможно, обходной путь с использованием встроенных программ, которые могут оказаться более быстрой альтернативой deepcopy.

Хотя new_list = old_list[:], copy.copy(old_list)' и для Py3k old_list.copy() работают для одноуровневых списков, они возвращаются к указанию на объекты list, вложенные в old_list и <884938980799> и <884938980799 из list объектов увековечены в другом.

Изменить: появилась новая информация

Как указали Аарон Холл и PM 2Ring , использование eval() не только плохая идея, но и намного медленнее, чем copy.deepcopy() .

Это означает, что для многомерных списков единственный вариант - copy.deepcopy(). С учетом сказанного, это действительно не вариант, поскольку производительность падает, когда вы пытаетесь использовать его на многомерном массиве среднего размера. Я попытался timeit использовать массив 42x42, что не является чем-то необычным или даже таким большим для приложений биоинформатики, и я отказался от ожидания ответа и просто начал вводить свою правку для этого сообщения.

Казалось бы, единственный реальный вариант - инициализировать несколько списков и работать с ними независимо. Если у кого-то есть другие предложения, как обрабатывать копирование многомерного списка, мы будем признательны.

Как утверждали другие, существуют существенные проблемы с производительностью при использовании модуля copy и copy.deepcopy copy для списков copy.deepcopy <8838980383809809809809809807998990> .

PM 2Ring
10 июля 2015 в 14:51
5

Это не всегда сработает, поскольку нет гарантии, что строка, возвращаемая repr(), достаточна для воссоздания объекта. Кроме того, eval() - это крайняя мера; подробнее см. Эвал действительно опасен ветеран SO Нед Батчелдер. Поэтому, когда вы выступаете за использование eval(), вы действительно ​​должны упомянуть, что это может быть опасно.

AMR
10 июля 2015 в 16:41
1

Честная оценка. Хотя я думаю, что точка зрения Батчелдера состоит в том, что наличие функции eval() в Python в целом является риском. Дело не столько в том, используете ли вы функцию в коде, сколько в том, что это дыра в безопасности в Python сама по себе. В моем примере он не используется с функцией, которая получает ввод от input(), sys.agrv или даже из текстового файла. Это больше похоже на инициализацию пустого многомерного списка один раз, а затем просто возможность скопировать его в цикле вместо повторной инициализации на каждой итерации цикла.

AMR
10 июля 2015 в 17:19
1

Как указал @AaronHall, вероятно, существует значительная проблема с производительностью при использовании new_list = eval(repr(old_list)), поэтому, помимо того, что это плохая идея, вероятно, он также слишком медленный, чтобы работать.

avatar
jack
23 ноября 2014 в 16:45
62

Уже есть много ответов, которые говорят вам, как сделать правильную копию, но ни один из них не говорит, почему ваша оригинальная «копия» не удалась.

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)

См. документацию для получения дополнительной информации о угловых случаях при копировании.

MrZH6
10 сентября 2020 в 15:37
0

Когда я занимался этой темой, мне следовало прокрутить вниз до вашего ответа. Мне было трудно понять, почему моя копия "списка списков" на самом деле не является копией ... :-) Спасибо за это!

avatar
Aaron Hall
25 октября 2014 в 12:13
138

Какие есть варианты клонирования или копирования списка в 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))
  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
John Locke
10 января 2019 в 12:44
2

Вам не нужна глубокая копия, если список двухмерный. Если это список списков, и в этих списках нет списков внутри них, вы можете использовать цикл for. В настоящее время я использую list_copy=[] for item in list: list_copy.append(copy(item)), и это намного быстрее.

avatar
anatoly techtonik
23 июля 2013 в 12:32
173

Мне сказали, что Python 3.3+ добавляет метод list.copy(), который должен быть таким же быстрым, как нарезка:

newlist = old_list.copy()
CyberMew
25 сентября 2018 в 18:07
12

Да, и согласно документам docs.python.org/3/library/stdtypes.html#mutable-sequence-types, s.copy() создает мелкую копию s (так же, как <768480) .

loved.by.Jesus
24 апреля 2020 в 08:11
4

На самом деле кажется, что в настоящее время python3.8, .copy() немного быстрее , чем нарезка. См. Ниже ответ @AaronsHall.

ShadowRanger
30 ноября 2020 в 18:33
0

@ Love.by.Jesus: Да, они добавили оптимизацию для вызовов методов уровня Python в 3.7, которые были расширены до вызовов методов расширения C в 3.8 с помощью PEP 590, что устраняет накладные расходы на создание связанный метод каждый раз, когда вы вызываете метод, поэтому стоимость вызова alist.copy() теперь составляет поиск dict для типа list, а затем относительно дешевый вызов функции без аргументов, который в конечном итоге вызывает то же самое, что и нарезка. Нарезка по-прежнему должна создать объект slice, а затем пройти проверку типа и распаковку, чтобы сделать то же самое.

ShadowRanger
30 ноября 2020 в 18:36
3

Конечно, они работают над оптимизацией повторяющихся построений постоянных срезов, поэтому в 3.10 срезы снова могут выиграть. Хотя все это довольно бессмысленно; асимптотическая производительность идентична, а фиксированные накладные расходы относительно невелики, поэтому на самом деле не имеет значения, какой подход вы используете.

avatar
cryo
10 апреля 2010 в 10:16
684

Феликс уже дал отличный ответ, но я подумал, что проведу сравнение скорости различных методов:

  1. 10,59 с (105,9 мкс / итн) - copy.deepcopy(old_list)
  2. 10,16 с (101,6 мкс / итн) - чистый метод Python Copy(), копирующий классы с глубокой копией
  3. 1,488 сек (14,88 мкс / итн) - чистый метод Python Copy(), не копирующий классы (только словари / списки / кортежи)
  4. 0,325 с (3,25 мкс / итн) - for item in old_list: new_list.append(item)
  5. 0,217 с (2,17 мкс / итн) - [i for i in old_list] (понимание списка)
  6. 0,186 с (1,86 мкс / итн) - copy.copy(old_list)
  7. 0,075 с (0,75 мкс / итн) - list(old_list)
  8. 0,053 с (0,53 мкс / итн) - 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
zzz777
4 мая 2021 в 18:24
0

Означает ли это, что добавление и понимание списка - лучшие варианты?

zzz777
4 мая 2021 в 18:32
0

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

avatar
erisco
10 апреля 2010 в 08:53
35

Идиома Python для этого: newList = oldList[:]

avatar
Paul Tarjan
10 апреля 2010 в 08:53
40

Используйте thing[:]

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