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