Рекурсивное копирование файлов или каталогов в Python

117

Кажется, в Python есть функции для копирования файлов (например shutil.copy) и функции для копирования каталогов (например shutil.copytree), но я не нашел ни одной функции, которая обрабатывала бы и то, и другое. Конечно, легко проверить, хотите ли вы скопировать файл или каталог, но это кажется странным упущением.

Неужели нет стандартной функции, которая работает как команда unix cp -r, т.е. поддерживает как каталоги, так и файлы и копирует рекурсивно? Что было бы наиболее элегантным способом решения этой проблемы в Python?

Pafcu
источник
3
Да это лажа. Одно из тех мест, где Python, пытаясь отразить основные системные вызовы, ухудшает видимый интерфейс. Хотя переключаться между копируемым файлом и копируемым деревом несложно, в этом не должно быть необходимости. Может быть, отправить запрос на улучшение в трекере ошибок Python, чтобы разрешить copytreeкопирование одного файла?
bobince 03

Ответы:

143

Я предлагаю вам сначала позвонить shutil.copytree, а если возникнет исключение, повторите попытку с помощью shutil.copy.

import shutil, errno

def copyanything(src, dst):
    try:
        shutil.copytree(src, dst)
    except OSError as exc: # python >2.5
        if exc.errno == errno.ENOTDIR:
            shutil.copy(src, dst)
        else: raise
цот
источник
18
Я думаю, было бы намного проще просто проверить, является ли src каталогом, используя os.path.isdir (src), вместо того, чтобы перехватывать подобное исключение. Или есть какая-то особая причина, по которой вместо этого следует использовать исключение?
pafcu 03
31
1) Потому что в мире Python EAFP (проще попросить прощения, чем разрешения) предпочтительнее LBYL (посмотрите, прежде чем прыгать). Я могу предоставить вам ссылки на эту тему, если это покажется вам в новинку. 2) Библиотечная функция уже косвенно проверяет это, так зачем тиражировать проверку? 3) ничто не мешает shutil.copytreeфункции улучшать и управлять обоими случаями в будущем. 4) Исключения в Python не такие уж и исключительные; например, итерация останавливается, вызывая исключение StopIteration.
tzot 03
2
Итак, в этом случае обработка исключения занимает 6 строк, а проверка типа занимает 4 строки. Немного, но в итоге складывается. Кроме того, как вы говорите, когда-нибудь copytree может также очень хорошо поддерживать файлы. Но невозможно сказать, на что будет похожа эта реализация. Может быть, в некоторых случаях, когда копирование работает, возникает исключение? В этом случае мой код внезапно перестал бы работать только из-за дополнительных функций. Но вы, вероятно, правы, исключения в Python довольно распространены, что меня очень раздражает, но, вероятно, потому, что я, кажется, никогда к этому не привыкаю
pafcu
5
На самом деле исключения имеют одно явное объективное преимущество в этом случае: вполне возможно (хотя и маловероятно), что тип меняется между проверкой и вызовом правильной функции.
pafcu
2
По моему личному мнению, добавление основных функций в исключение - плохая практика, независимо от того, какой язык вы используете. он помещает функциональность туда, где многие разработчики не будут искать. кроме того, если вы не напишете комментарий, менее опытный разработчик python не поймет, какова цель этой попытки. и если вам нужно добавить комментарий для такой тривиальной вещи, как здесь, что-то в вашем стиле кода не так. наконец, написание if / else приведет к тому, что код будет намного легче читать.
this.myself
7

Чтобы добавить на Tzot - х и GNS ответов, вот альтернативный способ копирования файлов и папок рекурсивно. (Python 3.X)

import os, shutil

root_src_dir = r'C:\MyMusic'    #Path/Location of the source directory
root_dst_dir = 'D:MusicBackUp'  #Path to the destination folder

for src_dir, dirs, files in os.walk(root_src_dir):
    dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
    if not os.path.exists(dst_dir):
        os.makedirs(dst_dir)
    for file_ in files:
        src_file = os.path.join(src_dir, file_)
        dst_file = os.path.join(dst_dir, file_)
        if os.path.exists(dst_file):
            os.remove(dst_file)
        shutil.copy(src_file, dst_dir)

Если это ваш первый раз и вы не знаете, как рекурсивно копировать файлы и папки, я надеюсь, что это поможет.

mondieki
источник
3

shutil.copyи shutil.copy2копируем файлы.

shutil.copytreeкопирует папку со всеми файлами и вложенными папками. shutil.copytreeиспользуется shutil.copy2для копирования файлов.

Таким образом, аналог того, что cp -rвы говорите, - это shutil.copytreeпотому, что cp -rцели и копируют папку и ее файлы / подпапки, например shutil.copytree. Без -r cpкопий файлы нравятся shutil.copyи shutil.copy2делают.

GMS
источник
1
Не думаю, что вы поняли вопрос. Попробуй shutil.copytree('C:\myfile.txt', 'C:\otherfile'). Не работает. Это то, о чем спрашивал ОП ... 7 лет назад.
Жан-Франсуа Корбетт,
Конечно не работает. Как будто cp не работает с папками. Нужна особая опция. copy и copytree - лучший способ справиться с копированием. Если бы copytree мог нацеливаться и на файлы, было бы легко ошибиться. Вы должны знать, на что нацелены как в Linux, так и в Python. Это сложно. Плюс кто-то еще прокомментировал это здесь, но, увидев вопрос и ответы, не смог удержаться от ответа. Элегантный способ - знать, что вы хотите сделать, а не универсальную копию без какого-либо контроля.
GMS
2

Unix cpне «поддерживает и каталоги, и файлы»:

betelgeuse:tmp james$ cp source/ dest/
cp: source/ is a directory (not copied).

Чтобы заставить cp скопировать каталог, вы должны вручную сообщить cp, что это каталог, используя флаг '-r'.

Однако здесь есть некоторая несогласованность - cp -rпри передаче имени файла в качестве источника с радостью копирует только один файл; copytree не будет.

Джеймс Полли
источник
2
docs.python.org/library/shutil.html включает код для copytree (), который демонстрирует обработку обычных файлов, символических ссылок и каталогов.
Джеймс Полли,
1
Этот ответ не отвечает на вопрос. Это должен быть комментарий, а не ответ.
Жан-Франсуа Корбетт,
0

Я думаю, что copy_tree - это то, что вы ищете

алгоритмы
источник
-2

Метод python shutil.copytree - это беспорядок. Я сделал то, что работает правильно:

def copydirectorykut(src, dst):
    os.chdir(dst)
    list=os.listdir(src)
    nom= src+'.txt'
    fitx= open(nom, 'w')

    for item in list:
        fitx.write("%s\n" % item)
    fitx.close()

    f = open(nom,'r')
    for line in f.readlines():
        if "." in line:
            shutil.copy(src+'/'+line[:-1],dst+'/'+line[:-1])
        else:
            if not os.path.exists(dst+'/'+line[:-1]):
                os.makedirs(dst+'/'+line[:-1])
                copydirectorykut(src+'/'+line[:-1],dst+'/'+line[:-1])
            copydirectorykut(src+'/'+line[:-1],dst+'/'+line[:-1])
    f.close()
    os.remove(nom)
    os.chdir('..')
Кутензо
источник
1
Этот код удобен для работы с проверкой отдельных файлов (проверьте перезапись), но не будет работать с бинарными файлами, такими как zip. Почему бы не использовать простую копию файла Python вместо построчного чтения / записи?
notilas