Copy в IT переводится как «копировать» (глагол) или «копия» (существительное), а в программировании обычно означает создание нового объекта или файла с тем же содержимым, что у исходного. Главная ловушка для начинающих — в Python не всякая «копия» независима от оригинала. Ниже разберу перевод кратко, затем три разных действия: присваивание, поверхностную и глубокую копию, и в конце — копирование файлов модулем shutil. Примеры проверены на Python 3.14.
Содержание
- Как переводится copy
- Мини-словарь: три действия, которые путают
- Минимальный пример: все три варианта сразу
- Какой способ выбрать
- Типичная ошибка: список списков через умножение
- Неизменяемые объекты и циклические ссылки
- Когда deepcopy не справляется
- copy.replace: копия с изменениями
- Копирование файлов: shutil
- Выводы
- Где применяется / связь с практикой
- FAQ
Как переводится copy
| Форма | Перевод | Пример в IT |
|---|---|---|
| to copy (глагол) | копировать, скопировать | copy a file — скопировать файл |
| a copy (существительное) | копия, экземпляр | a copy of the list — копия списка |
| copy (жаргон радиосвязи) | «принято», «понял» | ответ в чате команды |
| copy (маркетинг) | рекламный текст | отсюда copywriting |
Команда «Копировать» в интерфейсе — это Ctrl+C в Windows и Linux и Cmd+C в macOS, вставка — Ctrl+V / Cmd+V, вырезание — Ctrl+X / Cmd+X. Дальше речь только о копировании в коде.
Мини-словарь: три действия, которые путают
- Присваивание (
b = a) — копии нет, появляется второе имя того же объекта. - Поверхностная копия (shallow copy) — новый внешний объект, но вложенные объекты общие с оригиналом.
- Глубокая копия (deep copy) — новый объект и рекурсивно новые копии всего изменяемого внутри.
Разница видна только у изменяемых вложенных данных: списков, словарей, множеств, собственных объектов. Для списка чисел поверхностной копии достаточно.
Минимальный пример: все три варианта сразу
import copy
original = {"name": "Анна", "skills": ["python", "sql"]}
alias = original # присваивание: второе имя того же объекта
shallow = copy.copy(original) # поверхностная копия
deep = copy.deepcopy(original) # глубокая копия
original["name"] = "Мария"
original["skills"].append("git")
print(alias)
print(shallow)
print(deep)
print(alias is original, shallow is original, shallow["skills"] is original["skills"])
Результат запуска:
{'name': 'Мария', 'skills': ['python', 'sql', 'git']}
{'name': 'Анна', 'skills': ['python', 'sql', 'git']}
{'name': 'Анна', 'skills': ['python', 'sql']}
True False True
Разбор по строкам вывода:
aliasпоменялся целиком — это тот же словарь,alias is originalдаетTrue.- У
shallowимя осталось «Анна»: ключnameв копии указывает на свою строку. Но список навыков общий, поэтомуgitпоявился и здесь. deepне изменился вообще — списокskillsв нем свой.
Аналогия: присваивание — второй ярлык на ту же папку; поверхностная копия — новая папка с ярлыками на те же документы; глубокая копия — новая папка с копиями документов.
Какой способ выбрать
| Задача | Что взять | Когда иначе |
|---|---|---|
| Передать объект дальше без копии | присваивание b = a |
если получатель будет менять данные, а оригинал нужен прежним |
| Плоский список или словарь (числа, строки) | lst.copy(), lst[:], list(lst), dct.copy(), copy.copy() |
если внутри есть списки или словари, которые будут меняться |
| Вложенная изменяемая структура | copy.deepcopy() |
большие структуры: deepcopy медленнее, иногда проще пересобрать данные явно |
| Объект с полями, нужна версия с другими значениями | copy.replace() (Python 3.13+) |
учтите, что replace делает поверхностную копию |
Все способы из второй строки дают поверхностную копию. Проверка на Python 3.14: nums.copy(), nums[:], list(nums) и copy.copy(nums) создают новый список, равный исходному и не совпадающий с ним по is.
Типичная ошибка: список списков через умножение
Частый случай «неявного копирования» — создание таблицы через *. Неверный код:
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)
Фактический результат:
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
Умножение внешнего списка не копирует внутренний: три строки таблицы — это три ссылки на один и тот же список. Исправление — создавать каждую строку заново:
grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 1
print(grid)
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
[0] * 3 внутри при этом безопасен: числа неизменяемы, общая ссылка на 0 ничего не ломает.
Неизменяемые объекты и циклические ссылки
Для неизменяемых объектов copy обычно не создает новый объект: копировать нечего, изменить оригинал все равно нельзя.
import copy
t = (1, 2, 3)
print(copy.copy(t) is t, copy.deepcopy(t) is t) # True True
t2 = (1, [2])
print(copy.deepcopy(t2) is t2) # False
node = {"name": "A"}
node["self"] = node
clone = copy.deepcopy(node)
print(clone["self"] is clone, clone is node) # True False
Здесь две границы. Кортеж со списком внутри deepcopy все же копирует — иначе вложенный список остался бы общим. И deepcopy корректно обрабатывает циклы: он ведет словарь memo уже скопированных объектов, поэтому ссылка на себя в копии указывает на копию, а не на оригинал, и бесконечной рекурсии нет. Совпадение is у неизменяемых — деталь реализации, логику программы на нем не строят.
Когда deepcopy не справляется
Глубокая копия опирается на тот же протокол, что и pickle. Объекты с системными ресурсами — блокировки, открытые файлы, сокеты, соединения с базой — так не копируются:
import copy, threading
cfg = {"retries": 3, "lock": threading.Lock()}
try:
copy.deepcopy(cfg)
except TypeError as e:
print("TypeError:", e)
TypeError: cannot pickle '_thread.lock' object
Это правильное поведение: две «копии» одной блокировки или одного соединения не имеют смысла. Решение — копировать только данные, а ресурс создавать заново. В своем классе это делается методом __deepcopy__:
import copy
class Buffer:
def __init__(self, data):
self.data = data
self.cache = {} # кэш не копируем, строим заново
def __deepcopy__(self, memo):
clone = Buffer(copy.deepcopy(self.data, memo))
return clone
b1 = Buffer([1, 2, 3])
b1.cache["sum"] = 6
b2 = copy.deepcopy(b1)
b2.data.append(4)
print(b1.data, b1.cache) # [1, 2, 3] {'sum': 6}
print(b2.data, b2.cache) # [1, 2, 3, 4] {}
memo нужно передавать дальше во вложенный deepcopy, иначе сломается обработка циклических ссылок. Для поверхностной копии аналогичный хук называется __copy__.
copy.replace: копия с изменениями
С Python 3.13 в модуле copy есть функция replace: она создает копию объекта с заменой указанных полей. Работает с dataclass, namedtuple и другими типами, которые поддерживают __replace__.
import copy
from dataclasses import dataclass, field
@dataclass
class Settings:
host: str
port: int
tags: list = field(default_factory=list)
base = Settings("localhost", 8000, ["dev"])
prod = copy.replace(base, host="example.com", port=443)
prod.tags.append("prod")
print(prod)
print(base)
print(prod.tags is base.tags)
Settings(host='example.com', port=443, tags=['dev', 'prod'])
Settings(host='localhost', port=8000, tags=['dev', 'prod'])
True
Вывод показывает ту же ловушку: replace — поверхностная копия, список tags общий. Если вложенные поля будут меняться, передайте новое значение явно: copy.replace(base, tags=list(base.tags)).
Копирование файлов: shutil
Для файлов в Python есть модуль shutil. Функции отличаются тем, что переносят вместе с содержимым:
| Функция | Что копирует | Аналог в командной строке |
|---|---|---|
shutil.copyfile(src, dst) |
только содержимое, dst — путь к файлу |
прямого аналога нет, ближе всего cat src > dst |
shutil.copy(src, dst) |
содержимое и права доступа; dst может быть папкой |
cp в Linux/macOS, copy в Windows |
shutil.copy2(src, dst) |
то же плюс метаданные, в том числе время изменения | cp -p |
shutil.copytree(src, dst) |
папку рекурсивно (внутри по умолчанию copy2) |
cp -r, robocopy |
Проверка разницы между copy и copy2 во временной папке:
import shutil, tempfile, os
from pathlib import Path
with tempfile.TemporaryDirectory() as tmp:
src = Path(tmp) / "report.txt"
src.write_text("итоги квартала\n", encoding="utf-8")
os.utime(src, (1_700_000_000, 1_700_000_000)) # старое время изменения
dst1 = shutil.copy(src, Path(tmp) / "copy.txt")
dst2 = shutil.copy2(src, Path(tmp) / "copy2.txt")
print(Path(dst1).stat().st_mtime == src.stat().st_mtime) # False
print(Path(dst2).stat().st_mtime == src.stat().st_mtime) # True
На что обратить внимание при работе с файлами:
shutil.copyиcopy2молча перезаписывают существующий файл назначения. Если это недопустимо, проверьтеPath(dst).exists()перед копированием.copytreeпадает, если папка назначения уже есть:FileExistsError. Слить содержимое в существующую папку позволяетdirs_exist_ok=True(с Python 3.8), но совпадающие файлы при этом тоже перезапишутся.- Символические ссылки
copytreeпо умолчанию разыменовывает и копирует содержимое цели. Сохранить сами ссылки — параметрsymlinks=True. Копируя папку из недоверенного источника, проверьте, куда ведут ссылки. - Сохранение метаданных зависит от ОС и файловой системы:
copy2старается, но владелец файла и часть атрибутов могут не перенестись.
Выводы
- Copy в IT — «копировать» или «копия»; в коде важно, насколько копия независима от оригинала.
b = aв Python не копирует объект, а дает ему второе имя.- Поверхностная копия (
copy.copy,.copy(), срез) создает новый внешний объект, вложенные изменяемые объекты остаются общими;copy.replaceтоже поверхностная. copy.deepcopyкопирует рекурсивно и справляется с циклами, но не копирует ресурсы вроде блокировок и соединений — для них пишут__deepcopy__.- Для файлов —
shutil:copy2сохраняет метаданные,copytreeкопирует папки, перезапись назначения происходит без предупреждения.
Где применяется / связь с практикой
Освойте тему на практике
Различие между ссылкой и копией встречается в Python постоянно: при передаче списков в функции, в значениях по умолчанию, в настройках приложения, при подготовке тестовых данных и обработке таблиц. Ошибки здесь не падают с исключением, а тихо портят данные, поэтому их лучше понимать заранее. Модель объектов, изменяемые и неизменяемые типы, работу с файлами и стандартной библиотекой последовательно разбирают на курсе Python Developer. Basic. Посмотреть формат занятий бесплатно можно на открытых уроках Otus.
FAQ
Чем list.copy() отличается от copy.copy(list)?
Для списка результат одинаковый — поверхностная копия. Метод .copy() есть у списков, словарей и множеств, а copy.copy() работает с любым объектом, поддерживающим протокол копирования.
Почему deepcopy медленный и можно ли без него?
Он обходит всю структуру и ведет словарь уже скопированных объектов. Если структура известна, быстрее пересобрать ее явно, например [row[:] for row in grid] для таблицы из списков чисел.
Копирует ли присваивание строку или число?
Нет, присваивание никогда не копирует. Но строки и числа неизменяемы, поэтому общая ссылка на них безопасна: любое «изменение» создает новый объект.



