Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs
Следующий
модуль, который мы рассмотрим из стандартной библиотеки языка Python, это copy. Он позволяет
создавать копии произвольных объектов. О чем здесь речь? Давайте предположим,
что в программе имеются следующие объекты:
import copy
lst = [1, 2, 3, 4] # список
d = {'k': 1, 'v': 2} # словарь
tp = ('a', 'b', 'c') # кортеж
s = "hello" # строка
a = 5 # число
Тогда их копии
можно попробовать создать с помощью функции copy() модуля copy следующим
образом:
lst_cp = copy.copy(lst)
d_cp = copy.copy(d)
tp_cp = copy.copy(tp)
s_cp = copy.copy(s)
a_cp = copy.copy(a)
Давайте
проверим, действительно ли везде создаются копии и какие данные хранят новые
объекты:
print(f"lst_cp: {id(lst_cp) == id(lst)}, {lst_cp}")
print(f"d_cp: {id(d_cp) == id(d)}, {d_cp}")
print(f"tp_cp: {id(tp_cp) == id(tp)}, {tp_cp}")
print(f"s_cp: {id(s_cp) == id(s)}, {s_cp}")
print(f"a_cp: {id(a_cp) == id(a)}, {a_cp}")
После выполнения
программы увидим следующую информацию:
lst_cp: False, [1, 2, 3, 4]
d_cp: False, {'k': 1, 'v': 2}
tp_cp: True, ('a', 'b', 'c')
s_cp:
True, hello
a_cp: True, 5
Как видите,
копии создаются только для изменяемых объектов (списка и словаря). Неизменяемые
(кортеж, строка, число) не дублируются, просто возвращается ссылка на них и
переменные tp_cp, s_cp, a_cp ссылаются на те
же самые объекты, что и переменные tp, s, a.
Также следует
иметь в виду, что функция copy() выполняет лишь поверхностное
копирование объекта. Что это значит? Давайте представим, что необходимо создать
копию следующего двумерного (вложенного) списка:
lst2D = [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
Применяя функцию
copy() к таким
данным:
получим
следующий результат:
То есть,
вложенные элементы списка будут общими для обоих списков lst2D и lst_c. И мы в этом
можем легко убедиться. Изменим какое-либо значение во вложенном списке:
и оно
сказывается на содержимом списка lst_c:
print(lst_c) # [[10, 2, 3], [4, 5, 6, 7], [8, 9]]
Этот же эффект
поверхностного копирования будет наблюдаться для любых вложенных иерархических
данных, не только списков, но например, и словарей и множеств.
Функция deepcopy()
Если же нам
нужно построить полную копию иерархического объекта (вроде вложенного списка),
то следует использовать другую функцию deepcopy() – глубокое
копирование. Эта функция по рекурсии перебирает все вложенные объекты и создает
их копии. В результате выполнения команды:
lst_c = copy.deepcopy(lst2D)
картина
копирования будет выглядеть следующим образом:
Имеем два
совершенно разных вложенных списка с полностью независимыми данными. И теперь,
изменение какого-либо значения в списке lst2D:
никак не
сказывается на списке lst_c:
print(lst_c) # [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
Функция deepcopy() также успешно
справляется с копированием и более сложно устроенных данных. Например:
obj1 = ({1, 2}, [8, 9])
obj2 = ((1, 2, ['a', 'b']), {'key1': 1, 'key2': 2})
obj_c1 = copy.deepcopy(obj1)
obj_c2 = copy.deepcopy(obj2)
При этом
копирование затронет и неизменяемые данные (в данном примере кортежи), так как
кортежи в копиях будут содержать другие, независимые объекты. А уровень
вложений может быть довольно глубоким.
Копирование объектов классов
Функции copy() и deepcopy() можно
применять и для копирования объектов произвольных классов. Пусть, например,
имеется следующий класс:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"{id(self)}: {self.x, self.y}: {id(self.x), id(self.y)}"
И создается
копия его объекта командой:
pt = Point(10, 20) # исходный объект
pt_cp = copy.copy(pt) # копия объекта pt
Что в итоге
получим? Выведем оба объекта в консоль:
Увидим строчки:
2820474102416:
(10, 20): (140705615017032, 140705615017352)
2820474102544:
(10, 20): (140705615017032, 140705615017352)
То есть, объект pt был
действительно скопирован, но все внутренние ссылки (атрибуты) в копии этого
объекта точно такие же, что и в исходном.
Функция deepcopy() для объекта pt даст ровно такой
же результат.
Конечно, в
данном примере переменные x, y – неизменяемые и
то, что они не копируются больших проблем, скорее всего, не вызовет. Но, что
будет, если вместо конкретных значений передавать список координат? Давайте
посмотрим. Перепишем класс Point в виде:
class Point:
def __init__(self, *crds):
self.crds = list(crds)
def __repr__(self):
return f"{id(self)}: {self.crds}: {id(self.crds)}"
Создадим объект
этого класса с его копией:
pt = Point(1, 2, 3) # crds = [1, 2, 3]
pt_cp = copy.copy(pt)
Выведем объекты
в консоль:
получим:
1324518367376:
[1, 2, 3]: 1324518367488
1324518369808:
[1, 2, 3]: 1324518367488
У нас снова два
разных объекта, но оба ссылаются на один и тот же список. Это значит, что изменение
координат в одном объекте:
приведет к
соответствующему изменению в другом объекте pt_cp.
Может быть
функция deepcopy() исправит
ситуацию и в полном объеме продублирует объект pt? Давайте
попробуем:
pt_cp = copy.deepcopy(pt)
Видим, что
теперь списки в объектах тоже разные:
2495598124560:
[1, 2, 3]: 2495598125248
2495598125328:
[1, 2, 3]: 2495598125376
То есть,
стандартное поведение функции deepcopy() действительно позволяет
создавать глубокие копии объектов классов.
На самом деле,
функция deepcopy() перебирает
словарь __dict__ с набором
локальных атрибутов объекта и при необходимости создает копии объектов, на
которые они ссылаются.
Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs