Копирование массива в Python. Метод copy для копирования массива. Представление массива

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

Работая с массивами в Python, вы наверняка сталкивались с ситуацией, когда при использовании некоторых функций, возвращающих какой-нибудь результат, с исходным массивом не происходит ничего. Как в примере ниже:

Всё дело в том, что в библиотеке NumPy есть два понятия касаемо массива: копия и представление. И это разные вещи. Посмотрите на код ниже:

На самом деле, всё просто. Когда мы выполняем присваивание b = a, никакого копирования данных на деле не происходит. В памяти компьютера всё так же один массив, а переменные a и b — это даже не переменные, а указатели, указывающие на одни и те же данные. Таким образом, мы обращаемся по разным указателям к одним и тем же данным в памяти и видим в результате одно и то же.

Хорошо, a и b являются указателями, но что тогда с переменной с? На деле, это тоже указатель, ссылающийся на ту же область памяти с данными, правда, представлены эти данные в иной форме. Вот мы и подошли к понятию представления массива, которое существует в NumPy. Действительно, те же данные можно представить в разной форме:

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

Присваивание не копирует массивы в Python

Итак, простое присваивание никаких копий массива не выполняет, и это первое, что стоит уяснить. Может показаться, что это всего лишь прихоть создателей Python и NumPy, но всё не так просто. Если бы ситуация обстояла иначе, мы бы работали с памятью напрямую, а отсутствие автоматического копирования во время присваивания — совсем небольшая плата за лёгкость и простоту языка программирования Python.

Давайте приведём ещё парочку примеров на эту тему:

Обратите внимание, что массивы a и b в действительности являются одним и тем же массивом с такими же данными и типом данных.

Таким образом, у нас есть массив b и массив a, но нельзя забывать о том, что это, по сути, один и тот же массив.

Так как же копировать массивы в Python?

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

После применения метода copy мы можем говорить о массивах a и b, как о разных массивах и копиях друг друга. Да, эти массивы имеют одинаковые данные, но эти данные не являются одними и теми же. Теперь действительно массив b является копией массива a, и именно это называется копированием массива в терминологии NumPy.

Представление массива

Итак, теперь мы знаем, как сделать копию массива посредством метода copy. Но бывают ситуации, когда нам не нужна копия массива, а нужен тот же массив но с иными размерами. Речь идёт, как вы уже догадались, о другом представлении исходного массива.

Для этих целей в NumPy есть метод ndarray.view(). Он создаёт новый объект массива, просматривающий данные исходного, однако изменение размеров одного массива не приводит к изменению размеров другого.

Обычно, функции которые меняют форму и порядок элементов в Пайтон-массивах возвращают не копию массива, а именно его представление:

Также представлениями массивов в Python являются срезы массивов:

Обратите внимание, что когда мы говорим о том, что массив b является представлением массива a, мы подразумеваем, что вне зависимости от вида и формы массива b он включает в себя те же данные в памяти, что и наш массив a. Таким образом, изменение элементов в одном из массивов приведёт, соответственно, к изменениям в другом.

Глубокое и поверхностное копирование объектов с помощью copy

Как мы уже хорошо уяснили, операция присваивания не приводит к копированию объекта, а лишь создаёт ссылку на этот объект. Но если мы работаем с изменяемыми коллекциями или коллекциями, которые содержат изменяемые элементы, нам может понадобиться такая копия, которую мы сможем изменить, не меняя оригинал. Здесь нам тоже поможет copy, выполняющий как поверхностное, так и глубокое копирование: • copy.copy(a) — возвращает поверхностную копию a; • copy.deepcopy(a) — возвращает полную копию a.

Если же объект скопировать невозможно, возникает исключение copy.error. В принципе, разница между глубоким и поверхностным копированием существенна лишь для составных объектов, которые содержат изменяемые объекты (допустим, список списков). При этом: 1) поверхностная копия позволяет создать новый составной объект, а потом (если это возможно) вставляет в него ссылки на объекты, которые находятся в оригинале; 2) глубокая копия позволяет создать новый составной объект, а потом рекурсивно вставляет в него копии объектов, которые находятся в оригинале.

>>> import copy
>>> test_1 = [1, 2, 3, [1, 2, 3]]
>>> test_copy = copy.copy(test_1)
>>> print(test_1, test_copy)
[1, 2, 3, [1, 2, 3]] [1, 2, 3, [1, 2, 3]]
>>> test_copy[3].append(4)
>>> print(test_1, test_copy)
[1, 2, 3, [1, 2, 3, 4]] [1, 2, 3, [1, 2, 3, 4]]
>>> test_1 = [1, 2, 3, [1, 2, 3]]
>>> test_deepcopy = copy.deepcopy(test_1)
>>> test_deepcopy[3].append(4)
>>> print(test_1, test_deepcopy)
[1, 2, 3, [1, 2, 3]] [1, 2, 3, [1, 2, 3, 4]]

При выполнении глубокого копирования возможны проблемы (их нет у поверхностного копирования): — рекурсивные объекты могут привести к рекурсивному циклу; — т. к. глубокая копия копирует всё, она способна скопировать слишком много, к примеру, административные структуры данных.

Однако в случае возникновения проблем нам поможет функция deepcopy, которая устраняет эти сложности: — посредством хранения "memo" словаря объектов; — позволяя классам, которые определяет пользователь, переопределять операцию копирования либо набор копируемых компонентов.

>>> r = [1, 2, 3]
>>> r.append(r)
>>> print(r)
[1, 2, 3, [...]]
>>> p = copy.deepcopy(r)
>>> print(p)
[1, 2, 3, [...]]

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

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

При написании материала использовались статьи: — «Модуль copy — поверхностное и глубокое копирование объектов»; — «Копии и представления массивов».