Как сделать копию массива python

Обновлено: 07.07.2024

Каковы варианты клонирования или new_list = my_list список в Python?

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

С new_list , у вас фактически нет двух списков. Назначение просто копирует ссылку на список, а не фактический список, поэтому оба my_list и list.copy() ссылаются на один и тот же список после назначения.

Чтобы на самом деле скопировать список, у вас есть различные возможности:

Вы можете использовать встроенный new_list = old_list.copy() метод (доступный с python 3.3):

Вы можете отрезать его:

По мнению Алекса Мартелли (по крайней мере, в 2007 году ), это странный синтаксис, и использовать его никогда не имеет смысла . ;) (По его мнению, следующий - более читаемый).

Вы можете использовать встроенную new_list = list(old_list) функцию:

Это немного медленнее, чем old_list потому, что он должен copy.deepcopy() сначала определить тип данных .

Очевидно, самый медленный и самый необходимый для памяти способ, но иногда неизбежный.

Пример:

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

  1. 10,59 сек (105,9 ед / мин) - Copy()
  2. 10.16 сек (101.6us / itn) - чистый for item in old_list: new_list.append(item) метод python, копирующий классы с помощью deepcopy
  3. 1.488 с (14.88us / itn) - чистый [i for i in old_list] метод python, не копирующий классы (только dicts / lists / tuples)
  4. 0,325 с (3,25 ед / дюйм) - copy.copy(old_list)
  5. 0.217 с (2.17us / itn) - list(old_list) ( понимание списка )
  6. 0,186 с (1,86 ед / мин) - new_list = []; new_list.extend(old_list)
  7. 0,075 с (0,75 ед / дюйм) - old_list[:]
  8. 0,053 с (0,53 ед / мин) - copy.copy()
  9. 0.039 сек (0.39us / itn) - list[:] ( сортировка списка )

(Вот сценарий, если кто-то заинтересован или хочет поднять какие-либо вопросы :)

EDIT : добавлены классы стиля старого стиля, а также диктофоны к эталонам и сделали версию python намного быстрее и добавили еще несколько методов, включая выражения списков и newlist = old_list.copy() .

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

В Python 3 можно сделать мелкую копию с помощью:

В Python 2 и 3 вы можете получить мелкую копию с полным фрагментом оригинала:

объяснение

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

Неверная копия списка

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

В Python 2 и 3. есть разные способы сделать это. Пути Python 2 также будут работать в Python 3.

Python 2

В Python 2 идиоматический способ создания мелкой копии списка состоит из полного фрагмента оригинала:

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

но использование конструктора менее эффективно:

Python 3

В Python 3 списки получают >>> 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 метод:

Создание другого указателя не делает копию

Используя >>> l = [[], [], []] >>> l_copy = l [:] >>> l_copy [[], [], []] >>> l_copy [0] .append (' foo ') >>> l_copy [[' foo '], [], []] >>> l [[' foo '], [], []] = deepcopy затем модифицирует копию каждый раз при копировании копии 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 , что не делаете копию, вы просто добавляете другое имя, указывающее на этот исходный список в памяти. У нас могут быть подобные проблемы, когда мы делаем копии списков.

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

Глубокие копии

Чтобы продемонстрировать, как это позволяет нам создавать новые под-списки:

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

Не использовать my_list

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

  1. Это опасно, особенно если вы оцениваете что-то из источника, которому вы не доверяете.
  2. Это ненадежно, если подэлемент, который вы копируете, не имеет представления, которое может быть доказано для воспроизведения эквивалентного элемента.
  3. Он также менее эффективен.

В 64-битном Python 2.7:

на 64-битном Python 3.5:

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

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

Это еще не глубокая копия, потому что каждый элемент списка может ссылаться на другие объекты, точно так же, как список привязан к его элементам. Чтобы рекурсивно скопировать каждый элемент в списке, а затем каждый другой объект, на который ссылаются каждый элемент, и т. Д .: выполните глубокую копию.

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

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

Общее представление о массиве

Массив (в питоне еще принято название "список", это то же самое) — это переменная, в которой хранится много значений. Массив можно представлять себе в виде такой последовательности ячеек, в каждой из которых записано какое-то число:

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

a = [7, 5, -3, 12, 2, 0]

Теперь переменная a хранит этот массив. К элементам массива можно обращаться тоже через квадратные скобки: a[2] — это элемент номер 2, т.е. в нашем случае это -3 . Аналогично, a[5] — это 0. В квадратных скобках можно использовать любые арифметические выражения и даже другие переменные: a[2*2-1] — это 12, a[i] обозначает "возьми элемент с номером, равным значению переменной i ", аналогично a[2*i+1] обозначает "возьми элемент с номером, равным 2*i+1", или даже a[a[4]] обозначает "возьми элемент с номером, равным четвертому элементу нашего массива" (в нашем примере a[4] — это 2 , поэтому a[a[4]] — это a[2] , т.е. -3 ).

Если указанный номер слишком большой (больше длины массива), то питон выдаст ошибку (т.е. в примере выше a[100] будет ошибкой, да и даже a[6] тоже). Если указан отрицательный номер, то тут действует хитрое правило. Отрицательные номера обозначают нумерацию массива с конца: a[-1] — это всегда последний элемент, a[-2] — предпоследний и т.д. В нашем примере a[-6] равно 7. Слишком большой отрицательный номер тоже дает ошибку (в нашем примере a[-7] уже ошибка).

С элементами массива можно работать как с привычными вам переменными. Можно им присваивать значения: a[3] = 10 , считывать с клавиатуры: a[3] = int(input()) , выводить на экран: print(a[3]) , использовать в выражениях: a[3+i*a[2]] = 3+abs(a[1]-a[0]*2+i) (здесь i — какая-то еще целочисленная переменная для примера), использовать в if'ах: if a[i]>a[i-2]: , или for a[2] in range(i) и т.д. Везде, где вы раньше использовали переменные, можно теперь использовать элемент массива.

Обход массива

Но обычно вам надо работать сразу со всеми элементами массива. Точнее, сразу со всеми как правило не надо, надо по очереди с каждым (говорят: "пробежаться по массиву"). Для этого вам очень полезная вещь — это цикл for . Если вы знаете, что в массиве n элементов (т.е. если у вас есть переменная n и в ней хранится число элементов в массиве), то это делается так:

например, вывести все элементы массива на экран:

или увеличить все элементы массива на единицу:

и т.п. Конечно, в цикле можно и несколько действий делать, если надо. Осознайте, что это не магия, а просто полностью соответствует тому, что вы знаете про работу цикла for.

Если же у вас нет переменной n , то вы всегда можете воспользоваться специальной функцией len , которая возвращает количество элементов в массиве:

for i in range(len(a)): .

Функцию len , конечно, можно использовать где угодно, не только в заголовке цикла. Например, просто вывести длину массива — print(len(a)) .

Операции на массиве

Еще ряд полезных операций с массивами:

  • a[i] (на всякий случай повторю, чтобы было легче найти) — элемент массива с номером i .
  • len(a) (на всякий случай повторю, чтобы было легче найти) — длина массива.
  • a.append(x) — приписывает к массиву новый элемент со значением x , в результате длина массива становится на 1 больше. Конечно, вместо x может быть любое арифметическое выражение.
  • a.pop() — симметричная операция, удаляет последний элемент из массива. Длина массива становится на 1 меньше. Если нужно запомнить значение удаленного элемента, надо просто сохранить результат вызова pop в новую переменную: res = a.pop() .
  • a * 3 — это массив, полученный приписыванием массива a самого к себе три раза. Например, [1, 2, 3] * 3 — это [1, 2, 3, 1, 2, 3, 1, 2, 3] . Конечно, на месте тройки тут может быть любое арифметическое выражение. Самое частое применение этой конструкции — если вам нужен массив длины n , заполненный, например, нулями, то вы пишете [0] * n .
  • b = a — присваивание массивов. Теперь в b записан тот же массив, что и в a . Тот же — в прямом смысле слова: теперь и a , и b соответствуют одному и тому же массиву, и изменения в b отразятся в a и наоборот. Еще раз, потому что это очень важно. Присваивание массивов (и вообще любых сложных объектов) в питоне не копирует массив, а просто обе переменные начинают ссылаться на один и тот же массив, и изменения массива через любую из них меняет один и тот же массив. При этом на самом деле тут есть многие тонкости, просто будьте готовы к неожиданностям.
  • b = a[1:4] ("срез") — делает новый массив, состоящий из элементов старого массива начиная со первого (помните про нумерацию с нуля!) и заканчивая третьим (т.е. до четвертого, но не включительно, аналогично тому, как работает range ); этот массив сохраняется в b . Для примера выше получится [5, -3, 12] . Конечно, на месте 1 и 4 может быть любое арифметическое выражение. Более того, эти индексы можно вообще не писать, при этом автоматически подразумевается начало и конец массива. Например, a[:3] — это первые три элемента массива (нулевой, первый и второй), a[1:] — все элементы кроме нулевого, a[:-1] — все элементы кроме последнего (!), а a[:] — это копия всего массива. И это именно копия, т.е. запись b = a[:] именно копирует массив, получающиеся массивы никак не связаны, и изменения в b не влияют на a (в отличие от b = a ).

Ввод-вывод массива

Как вам считывать массив? Во-первых, если все элементы массива задаются в одной строке входного файла. Тогда есть два способа. Первый — длинный, но довольно понятный:

Второй — покороче, но попахивает магией:

Может показаться страшно, но на самом деле map(int, input().split()) вы уже встречали в конструкции

когда вам надо было считать два числа из одной строки. Это считывает строку ( input() ), разбивает по пробелам ( .split() ), и превращает каждую строку в число ( map(int, . ) ). Для чтения массива все то же самое, только вы еще заворачиваете все это в list(. ) , чтобы явно сказать питону, что это массив.

Какой из этих двух способов использовать для чтения данных из одной строки — выбирать вам.

Обратите внимание, что в обоих способах вам не надо знать заранее, сколько элементов будет в массиве — получится столько, сколько чисел в строке. В задачах часто бывает что задается сначала количество элементов, а потом (обычно на следующей строке) сами элементы. Это удобно в паскале, c++ и т.п., где нет способа легко считать числа до конца строки; в питоне вам это не надо, вы легко считываете сразу все элементы массива до конца строки, поэтому заданное число элементов вы считываете, но дальше не используете:

Еще бывает, что числа для массива задаются по одному в строке. Тогда вам проще всего заранее знать, сколько будет вводиться чисел. Обычно как раз так данные и даются: сначала количество элементов, потом сами элементы. Тогда все вводится легко:

Более сложные варианты — последовательность элементов по одному в строке, заканчивающаяся нулем, или задано количество элементов и сами элементы в той же строке — придумайте сами, как сделать (можете подумать сейчас, можете потом, когда попадется в задаче). Вы уже знаете все, что для этого надо.

Как выводить массив? Если надо по одному числу в строку, то просто:

Если же надо все числа в одну строку, то есть два способа. Во-первых, можно команде print передать специальный параметр end=" " , который обозначает "заканчивать вывод пробелом (а не переводом строки)":

Есть другой, более простой способ:

Эта магия обозначает вот что: возьми все элементы массива a и передай их отдельными аргументами в одну команду print . Т.е. получается print(a[0], a[1], a[2], . ) .

Двумерные массивы

Выше везде элементами массива были числа. Но на самом деле элементами массива может быть что угодно, в том числе другие массивы. Пример:

Что здесь происходит? Создаются три обычных массива a , b и c , а потом создается массив z , элементами которого являются как раз массивы a , b и c .

Что теперь получается? Например, z[1] — это элемент №1 массива z , т.е. b . Но b — это тоже массив, поэтому я могу написать z[1][2] — это то же самое, что b[2] , т.е. -3 (не забывайте, что нумерация элементов массива идет с нуля). Аналогично, z[0][2]==30 и т.д.

То же самое можно было записать проще:

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

z содержит три элемента, и не важно, что каждый из них тоже массив), а len(z[2]) — длина внутреннего массива на позиции 2 (т.е. 2 в примере выше). Для массива x выше (того, у которого каждый подмассив имеет свою длину) получим len(x)==5 , и, например, len(x[3])==0 .

Аналогично работают все остальные операции. z.append([1,2]) приписывает к "внешнему" массиву еще один "внутренний" массив, а z[2].append(3) приписывает число 3 к тому "внутреннему" массиву, который находится на позиции 2. Далее, z.pop() удаляет последний "внутренний" из "внешнего" массива, а z[2].pop() удаляет последний элемент из "внутреннего" массива на позиции 2. Аналогично работают z[1:2] и z[1][0:1] и т.д. — все операции, которые я приводил выше.

Обход двумерного массива

Конечно, чтобы обойти двумерный массив, надо обойти каждый его "внутренний" массив. Чтобы обойти внутренний массив, нужен цикл for , и еще один for нужен, чтобы перебрать все внутренние массивы:

Создание пустого массива

Неожиданно нетривиальная операция на двумерных массивах — это создание двумерного массива определенного размера, заполненного, например, нулями. Вы помните, что одномерный массив длины n можно создавать как [0] * n . Возникает желание написать a = ([0] * m) * n , чтобы создать двумерный массив размера n x m (мы хотим, чтобы первый индекс массива менялся от 0 до n-1 , а второй индекс до m-1 , поэтому это именно ([0] * m) * n , а не ([0] * n) * m ). Но это сработает не так, как вы можете думать. Дело опять в том, что в питоне массивы по умолчанию не копируются полностью, поэтому то, что получается — это массив длина n , в котором каждый элемент соответствует одному и тому же массиву длины n . В итоге, если вы будете менять, например, a[1][2] , то так же будет меняться и a[0][2] , и a[3][2] и т.д. — т.к. все внутренние массивы на самом деле соответствуют одному и тому же массиву.

Поэтому массив размера n x m делается, например, так:

мы вручную n раз приписали к массиву a один и тот же массив.

Или еще есть магия в одну строчку:

a = [[0] * m for i in range(n)]

Я пока не буду объяснять, как это работает, просто можете запомнить. Или пользоваться предыдущим вариантом.

Обратите внимание, что тут важный момент — хотим мы, чтобы n соответствовало первому индексу или второму. В примерах выше n — размер первого индекса (т.е. размер "внешнего" массива), a m — размер второго индекса (т.е. размер каждого "внутреннего" массива). Если вы хотите, то можно делать и наоборот, но это вы сами должны решить и делать согласованно во всей программе.

Ввод-вывод двумерного массива

Обычно двумерный массив вам задается как n строк по m чисел в каждой, причем числа n и m вам задаются заранее. Такой двумерный массив вводится эдакой комбинацией двух способов ввода одномерного массива, про которые я писал выше:

Мы считываем очередную строку и получаем очередной "внутренний" массив: list(map(int, input().split())) , и приписываем его ( append ) ко внешнему массиву.

Обратите внимание, что здесь мы уже четко решили, что первый индекс нашего массива соответствует строкам входного файла, а второй индекс — столбцам, т.е. фактически мы уже выбрали левую из двух картинок выше. Но это связано не с тем, как питон работает с двумерными массивами, а с тем, как заданы входные данные во входном файле.

Вывод двумерного массива, если вам его надо вывести такой же табличкой, тоже делается комбинацией способов вывода одномерного массива, например, так:

Многомерные массивы

Аналогично двумерным, бывают и трехмерные и т.д. массивы. Просто каждый элемент "внутреннего" массива теперь сам будет массивом:

Здесь a[0] — это двумерный массив [[1, 2], [3, 4]] , и a[1] — двумерный массив [[5, 6], [7, 8]] . Например, a[1][0][1] == 6 .

Многомерные массивы в простых задачах не нужны, но на самом деле бывают полезны и не представляют из себя чего-то особо сложного. С ними все аналогично тому, что мы обсуждали про двумерные массивы.

Списки и словари имеют метод copy , который возвращает поверхностные копии. Например:

Итак, у нас есть список, который содержит другой список. Затем мы создаем копию с помощью метода copy . Вопрос: полученная копия и исходный список — это разные объекты? Ответ — да:

А теперь попробуем изменить содержимое списка sublist и посмотрим, что случится:

Изменение содержимого вложенного списка повлекло за собой изменение как copy_list , так и outer_list . Такое поведение очень не очевидно. Казалось бы, эти два объекта различны, но изменение вложенного списка поменял исходный объект и копию. Такие копии называются поверхностными.

На самом деле модификация вложенного списка не изменяет содержимое ни copy_list , ни outer_list . Просто третий элемент обоих объектов связан с другим списком, который в свою очередь может быть модифицирован. В терминах языка Си мы бы говорили о наличие указателя.

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

Как ещё можно создавать поверхностные копии

Работая со списками, часто приходится выполнять срезы (slicing, о котором мы говорили тут). Для создания поверхностной копии можно использовать данную технику:

Собственно мы получаем тот же самый результат, что и при вызове copy , т.е. создается поверхностная копия.

Или пример создания списка списка:

— который приводит к тому же результату. То же самое можно применить и к словарю:

Глубокое копирование в Python

Если вам требуется копирование такие копии, внутренние объекты которых не связаны с чем-то внешним, то вам понадобится глубокое копирование.

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

Мы теперь можем воспользоваться этой функцией для копирования списка:

Как можно увидеть, изменение списка sublist не повлекло за собой изменение копии.

Для больших списков такая функция не будет работать с приемлемой скоростью, но к счастью в Python есть модуль, который реализуется и поверхностное, и глубокое копирование.

Модуль copy и функция deepcopy

Стандартный Python-модуль copy имеет две нужные нам функции [2]:

  • copy для поверхностного копирования;
  • deepcopy для глубокого.

Функция deepcopy достаточно умна, чтобы справляться с возникающими трудностями, которые могут возникнуть при копировании. Можете даже взглунять на количество проверок в исходном коде.

Если вы пишите со,ственные объекты и хотите указать, какое копирование использовать, то вам нужно реализовать протоколы __copy__ и/или __deepcopy__ .

О том, как происходит копирование данных большого размера (Big Data) вы узнаете на наших образовательных курсах в лицензированном учебном центре обучения и повышения квалификации руководителей и ИТ-специалистов (менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data) в Москве:


Основы

В Питоне нет структуры данных, полностью соответствующей массиву. Однако, есть списки, которые являются их надмножеством, то есть это те же массивы, но с расширенным функционалом. Эти структуры удобнее в использовании, но цена такого удобства, как всегда, производительность и потребляемые ресурсы. И массив, и список – это упорядоченные коллекции, но разница между ними заключается в том, что классический массив должен содержать элементы только одного типа, а список Python может содержать любые элементы.

Создание массива

Существует несколько способ создать массив. Ниже приведены примеры как это можно сделать.

Многомерный массив

Двухмерный массив в Python можно объявить следующим образом.

Операции с массивами

Давайте теперь рассмотрим операции, которые Пайтон позволяет выполнять над массивами.

Обход массива с использованием цикла for

Мы можем использовать цикл for для обхода элементов массива.


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

Обход многомерного массива

Для того чтоб получить элементы многомерного массива придётся использовать вложенные циклы.

Добавление

Мы можем использовать функцию insert() для вставки элемента по указанному индексу. Элементы из указанного индекса сдвигаются вправо на одну позицию.

Определение размера

Используйте метод len() чтобы вернуть длину массива (число элементов массива).
Не стоит путать размер массива с его размерностью!


Поскольку индексация элементов начинается с нуля, длина массива всегда на единицу больше, чем индекс последнего элемента.


Небольшое пояснение: метод списка .index() возвращает индекс элемента, значение которого совпадает с тем, которое передали методу. Здесь мы передаём значение последнего элемента и, таким образом, получаем индекс последнего элемента. Будьте осторожны: если в списке есть повторяющиеся значения, этот приём не сработает!

Срез Python предоставляет особый способ создания массива из другого массива.

Функция pop

В Python удалить ненужные элементы из массива можно при помощи метода pop, аргументом которого является индекс ячейки. Как и в случае с добавлением нового элемента, метод необходимо вызвать через ранее созданный объект.


После выполнения данной операции содержимое массива сдвигается так, чтобы количество доступных ячеек памяти совпадало с текущим количеством элементов.

Методы массива

В Python есть набор встроенных методов, которые вы можете использовать при работе с list.

Метод Значение
append() Добавляет элементы в конец списка
clear() Удаляет все элементы в списке
copy() Возвращает копию списка
count() Возвращает число элементов с определенным значением
extend() Добавляет элементы списка в конец текущего списка
index() Возвращает индекс первого элемента с определенным значением
insert() Добавляет элемент в определенную позицию
pop() Удаляет элемент по индексу
remove() Убирает элементы по значению
reverse() Разворачивает порядок в списке
sort() Сортирует список

Модуль array

Если Вам всё-таки нужен именно классический массив, вы можете использовать встроенный модуль array. Он почти не отличается от структуры list, за исключением, пожалуй, объявления.
Вот небольшая демонстрация:

Читайте также: