Как быстро копировать файлы

Копирование файлов с shutil.copyfile() занимает как минимум в 3 раза больше времени, чем обычное копирование правой кнопкой мыши> копирование правой кнопкой мыши и вставка с использованием Проводника Windows или Finder Mac. Есть ли более быстрая альтернатива shutil.copyfile() в Python? Что можно сделать, чтобы ускорить процесс копирования файлов? (Место назначения файлов находится на сетевом диске ... если это имеет значение ...).

ИЗМЕНЕНО ПОЗЖЕ:

Вот что у меня получилось:

def copyWithSubprocess(cmd):        
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

win=mac=False
if sys.platform.startswith("darwin"):mac=True
elif sys.platform.startswith("win"):win=True

cmd=None
if mac: cmd=['cp', source, dest]
elif win: cmd=['xcopy', source, dest, '/K/O/X']

if cmd: copyWithSubprocess(cmd)

person alphanumeric    schedule 27.02.2014    source источник
comment
Вы можете использовать собственные параметры командной строки, такие как cp для Linux и Mac и COPY для Windows. Они должны быть такими же быстрыми, как при использовании графического интерфейса.   -  person Ecno92    schedule 27.02.2014
comment
В Windows SHFileOperation предоставляет копию файла собственной оболочки   -  person David Heffernan    schedule 27.02.2014
comment
В зависимости от некоторых факторов, не указанных в вопросе, может быть полезно упаковать файлы в сжатый архив перед передачей ... Рассматривали ли вы использование чего-то вроде rsync?   -  person moooeeeep    schedule 27.02.2014
comment
Если вас беспокоит право собственности и ACL, не используйте shutil только по этой причине: 'В Windows владельцы файлов, списки управления доступом и альтернативные потоки данных не копируются.'   -  person Michael Burns    schedule 28.02.2014
comment
Если я использую команды собственной операционной системы (например, OSX cp), должен ли я использовать подпроцесс? Есть ли какой-либо модуль Python для прямого вызова cp без необходимости в подпроцессе на Mac?   -  person alphanumeric    schedule 28.02.2014
comment
Стоит отметить, что в Python 3.8 функции, копирующие файлы и каталоги оптимизированы для более быстрой работы на нескольких основных ОС.   -  person Morwenn    schedule 12.07.2018


Ответы (4)


Самая быстрая версия без чрезмерной оптимизации кода, который у меня есть, со следующим кодом:

class CTError(Exception):
    def __init__(self, errors):
        self.errors = errors

try:
    O_BINARY = os.O_BINARY
except:
    O_BINARY = 0
READ_FLAGS = os.O_RDONLY | O_BINARY
WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | O_BINARY
BUFFER_SIZE = 128*1024

def copyfile(src, dst):
    try:
        fin = os.open(src, READ_FLAGS)
        stat = os.fstat(fin)
        fout = os.open(dst, WRITE_FLAGS, stat.st_mode)
        for x in iter(lambda: os.read(fin, BUFFER_SIZE), ""):
            os.write(fout, x)
    finally:
        try: os.close(fin)
        except: pass
        try: os.close(fout)
        except: pass

def copytree(src, dst, symlinks=False, ignore=[]):
    names = os.listdir(src)

    if not os.path.exists(dst):
        os.makedirs(dst)
    errors = []
    for name in names:
        if name in ignore:
            continue
        srcname = os.path.join(src, name)
        dstname = os.path.join(dst, name)
        try:
            if symlinks and os.path.islink(srcname):
                linkto = os.readlink(srcname)
                os.symlink(linkto, dstname)
            elif os.path.isdir(srcname):
                copytree(srcname, dstname, symlinks, ignore)
            else:
                copyfile(srcname, dstname)
            # XXX What about devices, sockets etc.?
        except (IOError, os.error), why:
            errors.append((srcname, dstname, str(why)))
        except CTError, err:
            errors.extend(err.errors)
    if errors:
        raise CTError(errors)

Этот код работает немного медленнее, чем родной Linux "cp -rf".

По сравнению с shutil выигрыш для локального хранилища по сравнению с tmfps составляет около 2x-3x и около 6x для NFS для локального хранилища.

После профилирования я заметил, что shutil.copy выполняет множество syscals fstat, которые довольно тяжелы. Если кто-то хочет продолжить оптимизацию, я бы предложил сделать один fstat для src и повторно использовать значения. Честно говоря, я не пошел дальше, так как получил почти те же цифры, что и собственный инструмент копирования Linux, и оптимизация на несколько сотен миллисекунд не была моей целью.

person Dmytro    schedule 24.01.2015
comment
Не уверен, что это характерно для более поздних версий python (3.5+), но для остановки в iter должно быть указано b''. (По крайней мере, на OSX) - person muppetjones; 10.03.2017
comment
не работает с python 3.6 даже после исправления синтаксиса 'except' - person Vasily; 06.12.2017
comment
Это версия python2. Я не тестировал это на python3. Вероятно, собственная копия файлового дерева python3 работает достаточно быстро, нужно провести тест. - person Dmytro; 07.12.2017
comment
Это намного быстрее, чем все, что я пробовал. - person Soumyajit; 16.01.2018
comment
К вашему сведению, я бы предложил добавить shutil.copystat(src, dst) после того, как файл будет записан, чтобы передать метаданные. - person Spencer; 15.07.2018
comment
@Dmytro Только что протестировал в py3.6, все еще в 3 раза быстрее, чем shutil.copy2 ()! - person Spencer; 19.10.2018
comment
@Spencer Что вы изменили в коде, чтобы он работал с python 3.5+? С уважением! - person Varlor; 14.11.2018
comment
@Varlor Насколько я помню, я просто изменил эту строку на следующую: for x in iter(lambda: os.read(fin, BUFFER_SIZE), b""): (превратил строку в байты, добавив 'b') - person Spencer; 15.11.2018
comment
ах, потому что я также получаю синтаксическую ошибку с «почему» в строке except «except (IOError, os.error), why:» - person Varlor; 15.11.2018
comment
Чтобы повысить производительность, также просмотрите модуль pyfastcopy, который использует системный вызов sendfile (). Этот модуль работает для Python 2 и 3. Все, что вам нужно сделать, это буквально импортировать pyfastcopy, и shutils автоматически будет работать лучше. Как и @Morwenn, упомянутый выше, Python 3.8 будет иметь sendfile (), встроенный в имп. - person Vahid Pazirandeh; 12.12.2018
comment
Я пытался заменить shutil.copyfile на copyfile из примера с python 3.8.0, но похоже, что он завис в цикле for x in iter. - person Andry; 10.11.2019
comment
@VahidPazirandeh pyfastcopy не работает в Windows: Системный вызов sendfile не существует в Windows, поэтому импорт этого модуля не даст никакого эффекта. - person Andry; 10.11.2019

Вы можете просто использовать ОС, в которой делаете копию, для Windows:

from subprocess import call
call(["xcopy", "c:\\file.txt", "n:\\folder\\", "/K/O/X"])

/ K - Копирует атрибуты. Обычно Xcopy сбрасывает атрибуты, доступные только для чтения.
/ O - Копирует информацию о владельце файла и ACL.
/ X - Копирует настройки аудита файлов (подразумевает / O).

person Michael Burns    schedule 27.02.2014
comment
Будет ли xcopy в Windows работать с обычным подпроцессом, например: cmd = ['xcopy', source, dest, / K / O / X] subprocess.Popen (cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE) - person alphanumeric; 28.02.2014
comment
Это тоже сработает. - person Michael Burns; 28.02.2014
comment
Здорово! Спасибо за помощь! - person alphanumeric; 28.02.2014
comment
Ошибка неверного количества параметров - person ; 18.08.2017
comment
Обратите внимание, что флаги / O и / X требуют подпроцесса с повышенными правами, иначе вы получите отказ в доступе - person Rexovas; 14.12.2020

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

в питоне

copied_file = open("some_file").read()

эквивалент копии ctrl + c

тогда

with open("new_file","wb") as f:
     f.write(copied_file)

эквивалент пасты ctrl + v (так что время для эквивалентности ....)

если вы хотите, чтобы он был более масштабируемым для больших данных (но он не будет таким быстрым, как ctrl + v / ctrl + c

with open(infile,"rb") as fin,open(outfile,"wb") as fout:
     fout.writelines(iter(fin.readline,''))
person Joran Beasley    schedule 27.02.2014
comment
Я считаю, что вы были бы хорошим инструктором, приятно! - person Sharif Mamun; 28.02.2014
comment
Хорошая точка зрения. Я должен быть более конкретным. Вместо того, чтобы щелкнуть правой кнопкой мыши, скопировать и вставить: Эта схема: 1. Выберите файлы; 2. Перетащите файлы. 3 Перетащите файлы в папку назначения. - person alphanumeric; 28.02.2014
comment
это ход ... который сильно отличается ... вместо этого попробуйте shutil.move - person Joran Beasley; 28.02.2014
comment
В выигрыше это может быть ход. В osx это может быть копия. - person alphanumeric; 28.02.2014
comment
Это решение не масштабируется. По мере увеличения размера файлов это решение становится менее удобным. Вам нужно будет сделать несколько системных вызовов ОС, чтобы по-прежнему читать части файла в память по мере того, как файлы становятся большими. - person searchengine27; 21.02.2017
comment
это был вопрос о сроках ... и он в основном указывал на то, что его операции не были эквивалентны - person Joran Beasley; 22.02.2017
comment
Мне трудно поверить, что если вы CTRL + C 100-гигабайтный файл в Windows, он попытается загрузить его в память прямо сейчас ... - person Robert Kelly; 27.04.2017
comment
в этом я уверен больше, чем доля правды ... возможно, я слишком упрощал это - person Joran Beasley; 27.04.2017

person    schedule
comment
Экономичен в объяснениях, но это отличный ответ. - person Gustavo Gonçalves; 13.06.2020