Модуль copy — поверхностное и глубокое копирование объектов

Операция присваивания не копирует объект, он лишь создаёт ссылку на объект. Для изменяемых коллекций, или для коллекций, содержащих изменяемые элементы, часто необходима такая копия, чтобы её можно было изменить, не изменяя оригинал. Данный модуль предоставляет общие (поверхностная и глубокая) операции копирования.
copy.copy(x) — возвращает поверхностную копию x.
copy.deepcopy(x) — возвращает полную копию x.
Исключениеcopy.error — возникает, если объект невозможно скопировать.
Разница между поверхностным и глубоким копированием существенна только для составных объектов, содержащих изменяемые объекты (например, список списков, или словарь, в качестве значений которого — списки или словари):
- Поверхностная копия создает новый составной объект, и затем (по мере возможности) вставляет в него ссылки на объекты, находящиеся в оригинале.
- Глубокая копия создает новый составной объект, и затем рекурсивно вставляет в него копии объектов, находящихся в оригинале.
Для операции глубокого копирования часто возникают две проблемы, которых нет у операции поверхностного копирования:
- Рекурсивные объекты (составные объекты, которые явно или неявно содержат ссылки на себя) могут стать причиной рекурсивного цикла;
- Поскольку глубокая копия копирует всё, она может скопировать слишком много, например, административные структуры данных, которые должны быть разделяемы даже между копиями.
Функция deepcopy решает эти проблемы путем:
- Хранения «memo» словаря объектов, скопированных во время текущего прохода копирования;
- Позволения классам, определенным пользователем, переопределять операцию копирования или набор копируемых компонентов.
Этот модуль не копирует типы вроде модулей, классов, функций, методов, следа в стеке, стековых кадров, файлов, сокетов, окон, и подобных типов.
Поверхностная копия изменяемых объектов также может быть создана методом .copy() у списков (начиная с Python 3.3), присваиванием среза (copied_list = original_list[:]), методом .copy() словарей и множеств. Создавать копию неизменяемых объектов (таких, как, например, строк) необязательно (они же неизменяемые).
Для того, чтобы определить собственную реализацию копирования, класс может определить специальные методы __copy__() и __deepcopy__(). Первый вызывается для реализации операции поверхностного копирования; дополнительных аргументов не передается. Второй вызывается для реализации операции глубокого копирования; ему передается один аргумент, словарь memo. Если реализация __deepcopy__() нуждается в создании глубокой копии компонента, то он должен вызвать функцию deepcopy() с компонентом в качестве первого аргумента и словарем memo в качестве второго аргумента.
Для вставки кода на Python в комментарий заключайте его в теги
Копирование объектов в Python
Следует сразу сказать, что оператор присваивания = не создаёт копию объекта. Присваивание создаёт новую переменную, которая дублирует ссылку на исходный объект.
Для примера давайте создадим из старого списка новый список (путем присваивания).
old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 'a']] new_list = old_list new_list[2][2] = 9 print('Old List:', old_list) print('ID of Old List:', id(old_list)) print('New List:', new_list) print('ID of New List:', id(new_list))
Вывод будет следующим:
Old List: [[1, 2, 3], [4, 5, 6], [7, 8, 9]] ID of Old List: 140673303268168 New List: [[1, 2, 3], [4, 5, 6], [7, 8, 9]] ID of New List: 140673303268168
Мы видим, что у обеих переменных — old_list и new_list — один id (140673303268168). Если внести изменения в любой из этих список, изменятся оба. Но иногда нам нужно создать копию самого объекта, а не копию ссылки на него.
Для копирования объектов в Python используется модуль copy и следующие методы:
- copy() . Копирует объект и возвращает поверхностную копию передаваемого аргумента.
- deepcopy() . Тоже копирует объект, но возвращает полную копию передаваемого аргумента.
Чем отличаются глубокое и поверхностное копирование?
Поверхностное копирование
Поверхностное копирование создает отдельный новый объект или список, но вместо копирования дочерних элементов в новый объект, оно просто копирует ссылки на их адреса памяти. Следовательно, если вы сделаете изменение в исходном объекте, оно будет отражено в скопированном объекте, и наоборот.
Пример поверхностного копирования:
import copy old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] new_list = copy.copy(old_list) print("Old list:", old_list) print("New list:", new_list)
Old list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]] New list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Старый список и новый список — разные объекты. Чтобы это доказать, давайте изменим старый список:
import copy old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]] new_list = copy.copy(old_list) old_list.append([4, 4, 4]) print("Old list:", old_list) print("New list:", new_list)
Old list: [[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]] New list: [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
В этом примере мы создали поверхностную копию old_list. Новый список (new_list) содержит ссылки на исходные вложенные объекты, хранящиеся в старом списке. Когда мы добавили новый вложенный объект в old_list, это не отразилось на new_list, потому что в последнем не было ссылки на этот новый вложенный объект.
Давайте теперь попробуем изменить один из вложенных объектов, ссылки на которые были скопированы в new_list.
import copy old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]] new_list = copy.copy(old_list) old_list[1][1] = 'AA' print("Old list:", old_list) print("New list:", new_list)
Old list: [[1, 1, 1], [2, 'AA', 2], [3, 3, 3]] New list: [[1, 1, 1], [2, 'AA', 2], [3, 3, 3]]
Изменения затронули оба списка, потому что оба они содержат ссылки на один и тот же вложенный объект.
Глубокое (полное) копирование
Глубокая копия создает новую и отдельную копию всего объекта или списка со своим уникальным адресом памяти. Это означает, что любые изменения, внесенные вами в новую копию объекта или списка, не будут отражаться в исходной. Этот процесс происходит следующим образом: сначала создается новый список или объект, а затем рекурсивно копируются все элементы из исходного в новый.
Короче говоря, оба объекта становятся полностью независимы друг от друга. Это похоже на концепцию передачи по значению в таких языках, как C ++, Java и C #.
import copy old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]] new_list = copy.deepcopy(old_list) print("Old list:", old_list) print("New list:", new_list)
Old list: [[1, 1, 1], [2, 2, 2], [3, 3, 3]] New list: [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
Вроде все так же, как и при поверхностном копировании. Но поведение объектов будет отличаться. Давайте попробуем внести изменения в один из вложенных объектов старого списка:
import copy old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]] new_list = copy.deepcopy(old_list) old_list[1][0] = 'BB' print("Old list:", old_list) print("New list:", new_list)
Результат показывает, что изменения отразились только на старом списке:
Old list: [[1, 1, 1], ['BB', 2, 2], [3, 3, 3]] New list: [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
Так происходит потому, что при глубоком копировании копируются не ссылки на вложенные объекты, а сами объекты.
В посте использовались материалы статьи «Глубокое и поверхностное копирование в Python» и код из статьи «Python Shallow Copy and Deep Copy».
Копирование объектов, модуль copy
В Python изменяемые объекты нельзя скопировать, присвоив одну переменной другой, так как в этом случае копируется ссылка на объект, а не он сам. В итоге при изменении объекта через одну переменную, изменения видны через другую. Поэтому используются иные способы копирования, если оно действительно необходимо.
У списков, словарей и некоторых других встроенных типов есть метод copy() , создающий их поверхностную копию. В случае поверхностной копии, если объект является составным, то есть включает другие изменяемые объекты, то они не копируются, а копируются только ссылки на них.
Если же требуется полная копия объекта, следует воспользоваться функцией deepcopy() модуля copy . Кроме этой функции там также есть функция copy() , выполняющая поверхностное копирование, аналогичное методам copy() словарей и списков.
Разницу между copy() и deepcopy() иллюстрирует пример:
>>> import copy >>> nums = [1, 2, 3] >>> data = >>> data >>> data_copy = copy.copy(data) >>> data_deep = copy.deepcopy(data) >>> data_copy >>> data_deep >>> data_copy['a'] += 2 >>> nums[1:1] = [254] >>> data >>> data_copy >>> data_deep
В случае с deepcopy() была создана копия вложенного списка, copy() этого не делает.
Отсутствие переменной у списка вовсе не изменяет ситуацию:
>>> d = >>> c = copy.copy(d) >>> c >>> c[1].append(3) >>> c >>> d
Оператор is проверяет проверяет ссылаются ли две переменные на один объект, оператор == проверяет равенство значений.
>>> d = >>> c = d >>> e = d.copy() >>> d is c True >>> d is e False >>> d == e True
С помощью функций модуля copy можно копировать объекты собственных классов:
import copy class A: def __init__(self): self.lst = [] a = A() a.lst.append(10) b = copy.copy(a) b.lst[0] = 20 print(a.lst, b.lst) print(a is b) print(a) print(b)
[20] [20] False
Как мы видим, несмотря на то, что объекты a и b разные, поле lst обоих ссылается на один и тот же список. Чтобы при копировании объекта список был также скопирован, следует использовать функцию deepcopy() .
NumPy, часть 2: базовые операции над массивами

Здравствуйте! Я продолжаю работу над пособием по python-библиотеке NumPy.
В прошлой части мы научились создавать массивы и их печатать. Однако это не имеет смысла, если с ними ничего нельзя делать.
Сегодня мы познакомимся с операциями над массивами.
Базовые операции
Математические операции над массивами выполняются поэлементно. Создается новый массив, который заполняется результатами действия оператора.
:1: RuntimeWarning: divide by zero encountered in true_divide
Для этого, естественно, массивы должны быть одинаковых размеров.
File
Также можно производить математические операции между массивом и числом. В этом случае к каждому элементу прибавляется (или что вы там делаете) это число.
NumPy также предоставляет множество математических операций для обработки массивов:
Полный список можно посмотреть здесь.
Многие унарные операции, такие как, например, вычисление суммы всех элементов массива, представлены также и в виде методов класса ndarray.
Индексы, срезы, итерации
Одномерные массивы осуществляют операции индексирования, срезов и итераций очень схожим образом с обычными списками и другими последовательностями Python (разве что удалять с помощью срезов нельзя).
File У многомерных массивов на каждую ось приходится один индекс. Индексы передаются в виде последовательности чисел, разделенных запятыми (то бишь, кортежами):
Когда индексов меньше, чем осей, отсутствующие индексы предполагаются дополненными с помощью срезов:
b[i] можно читать как b[i, ]. В NumPy это также может быть записано с помощью точек, как b[i, . ].
Например, если x имеет ранг 5 (то есть у него 5 осей), тогда
- x[1, 2, . ] эквивалентно x[1, 2, :, :, :],
- x[. , 3] то же самое, что x[:, :, :, :, 3] и
- x[4, . , 5, :] это x[4, :, :, 5, :].
Итерирование многомерных массивов начинается с первой оси:
Однако, если нужно перебрать поэлементно весь массив, как если бы он был одномерным, для этого можно использовать атрибут flat:
Как уже говорилось, у массива есть форма (shape), определяемая числом элементов вдоль каждой оси:
Форма массива может быть изменена с помощью различных команд:
Порядок элементов в массиве в результате функции ravel() соответствует обычному "C-стилю", то есть, чем правее индекс, тем он "быстрее изменяется": за элементом a[0,0] следует a[0,1]. Если одна форма массива была изменена на другую, массив переформировывается также в "C-стиле". Функции ravel() и reshape() также могут работать (при использовании дополнительного аргумента) в FORTRAN-стиле, в котором быстрее изменяется более левый индекс.
Метод reshape() возвращает ее аргумент с измененной формой, в то время как метод resize() изменяет сам массив:
Если при операции такой перестройки один из аргументов задается как -1, то он автоматически рассчитывается в соответствии с остальными заданными:
Объединение массивов
Несколько массивов могут быть объединены вместе вдоль разных осей с помощью функций hstack и vstack.
hstack() объединяет массивы по первым осям, vstack() — по последним:
Функция column_stack() объединяет одномерные массивы в качестве столбцов двумерного массива:
Аналогично для строк имеется функция row_stack().
Разбиение массива
Используя hsplit() вы можете разбить массив вдоль горизонтальной оси, указав либо число возвращаемых массивов одинаковой формы, либо номера столбцов, после которых массив разрезается "ножницами":
Функция vsplit() разбивает массив вдоль вертикальной оси, а array_split() позволяет указать оси, вдоль которых произойдет разбиение.
Копии и представления
При работе с массивами, их данные иногда необходимо копировать в другой массив, а иногда нет. Это часто является источником путаницы. Возможно 3 случая:
Вообще никаких копий
Простое присваивание не создает ни копии массива, ни копии его данных:
Python передает изменяемые объекты как ссылки, поэтому вызовы функций также не создают копий.
Представление или поверхностная копия
Разные объекты массивов могут использовать одни и те же данные. Метод view() создает новый объект массива, являющийся представлением тех же данных.
Срез массива это представление:
Глубокая копия
Метод copy() создаст настоящую копию массива и его данных:
Для вставки кода на Python в комментарий заключайте его в теги
- Модуль csv - чтение и запись CSV файлов
- Создаём сайт на Django, используя хорошие практики. Часть 1: создаём проект
- Онлайн-обучение Python: сравнение популярных программ
- Книги о Python
- GUI (графический интерфейс пользователя)
- Курсы Python
- Модули
- Новости мира Python
- NumPy
- Обработка данных
- Основы программирования
- Примеры программ
- Типы данных в Python
- Видео
- Python для Web
- Работа для Python-программистов
- Сделай свой вклад в развитие сайта!
- Самоучитель Python
- Карта сайта
- Отзывы на книги по Python
- Реклама на сайте