5 способов копирования списка в Python

Очень часто приходится копировать списки в программах Python. Но что вам следует знать о копировании списков?

Как скопировать список Python?

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

Этот урок призван показать вам все, что вам нужно знать о копировании списков в Python.

Давайте начнем!

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

Я начну с простого примера, чтобы вместе понять, как работает копирование списков в Python.

После определения списка с именем numbers я использую оператор присваивания (=), чтобы скопировать этот список в новый список с именем new_numbers.

Посмотрим, что будет…

>>> numbers = [1,4,7,19,23]
>>> new_numbers = numbers

Теперь я добавляю новый элемент в список new_numbers с помощью метода append и проверяю элементы в обоих списках с помощью функции print:

>>> new_numbers.append(34)
>>> print(numbers)
[1, 4, 7, 19, 23, 34]
>>> print(new_numbers)
[1, 4, 7, 19, 23, 34]

По какой-то причине, даже если мы добавили новый номер только в список new_numbers, оба наших списка содержат новый номер.

Почему?

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

>>> hex(id(numbers))
'0x10d75e5a0'
>>> hex(id(new_numbers))
'0x10d75e5a0'

Интересный…

Видите ли вы проблему?

Обе переменные указывают на один и тот же адрес памяти, поэтому numbers и new_numbers указывают на один и тот же объект списка. Вот почему мы видим новый элемент в обоих из них.

Итак, как мы можем скопировать наш список в совершенно новый объект?

Как создать точную копию оригинального списка

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

Давайте применим метод копирования к нашему исходному списку, чтобы создать список new_numbers:

new_numbers = numbers.copy()

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

>>> new_numbers.append(34)
>>> print(numbers)
[1, 4, 7, 19, 23]
>>> print(new_numbers)
[1, 4, 7, 19, 23, 34]

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

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

>>> hex(id(numbers))
'0x10751d460'
>>> hex(id(new_numbers))
'0x10761d9b0'

Разные адреса памяти для двух объектов. Это хорошо!

Копирование с использованием обозначения срезов Python

Другой способ скопировать список Python — использовать нотацию среза.

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

new_list = original_list[:]

Давайте применим это к нашему списку чисел:

>>> new_numbers = numbers[:]

После добавления еще одного числа в новый список вы увидите, что исходный список снова не изменился:

>>> new_numbers.append(34)
>>> print(numbers)
[1, 4, 7, 19, 23]
>>> print(new_numbers)
[1, 4, 7, 19, 23, 34]

И с помощью обозначения среза мы создали новый объект списка:

>>> hex(id(numbers))
'0x105e92460'
>>> hex(id(new_numbers))
'0x105f925f0'

И это тоже готово! 🙂

Поверхностное копирование против глубокого копирования

Разница между поверхностной и глубокой копией применима только к составным объектам, то есть к объектам, которые содержат другие объекты.

Примерами составных объектов являются экземпляры классов и списки.

Модуль копирования Python позволяет создавать поверхностные и глубокие копии объектов. Ниже вы можете увидеть синтаксис для обоих типов копирования:

SHALLOW COPY: new_object = copy.copy(original_object)
DEEP COPY: new_object = copy.deepcopy(original_object)

При поверхностном копировании создается новый составной объект (например, список списков), а ссылки на объекты, найденные в исходном объекте, добавляются в новый составной объект.

В следующем разделе мы подробно рассмотрим, как работает поверхностное копирование.

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

Глубокое копирование создает новый составной объект (например, список списков), затем оно также создает копии объектов, найденных в исходном объекте, и вставляет их в новый составной объект.

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

Как сделать поверхностную копию в Python

Давайте посмотрим, как поверхностное копирование работает со списком…

…попробуйте выполнить эти команды в оболочке Python, чтобы убедиться, что поведение поверхностного и глубокого копирования вам понятно:

>>> import copy
>>> numbers = [[1,2,3], [4,5,6], [7,8,9]]
>>> new_numbers = copy.copy(numbers)

Если я добавлю элемент в список new_numbers, исходный список не изменится:

>>> new_numbers.append([10,11,12])
>>> numbers
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> new_numbers
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

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

Но теперь давайте попробуем обновить один элемент, общий для исходного и нового списков:

>>> new_numbers[0][0] = 4
>>> numbers
[[4, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> new_numbers
[[4, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

Я обновил первый элемент первого объекта списка в исходном списке.

Как видите, элемент обновился в обоих списках, исходном и новом.

Это связано с тем, что мы использовали поверхностное копирование, и поэтому первый элемент списка new_numbers — это просто ссылка на первый элемент списка numbers ([1,2,3]).

Как сделать глубокую копию в Python

Давайте создадим глубокую копию из того же списка списков из предыдущего раздела…

>>> import copy
>>> numbers = [[1,2,3], [4,5,6], [7,8,9]]
>>> new_numbers = copy.deepcopy(numbers)

Давайте еще раз добавим элемент в список new_numbers:

>>> new_numbers.append([10,11,12])
>>> numbers
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> new_numbers
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

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

Точно так же, как мы это делали в предыдущем разделе, давайте изменим первый элемент первого списка в списке new_numbers и посмотрим, что произойдет…

>>> new_numbers[0][0] = 4
>>> numbers
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> new_numbers
[[4, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

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

Теперь вы видите разницу между поверхностной и глубокой копией?

Заключение

В этом уроке вы узнали, как копировать список Python несколькими способами:

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

И теперь вы также знаете, как избежать ошибок, вызванных тем, как Python обрабатывает копирование изменяемых коллекций, таких как списки 🙂