Рубрики
Без рубрики

Python Putil: файловые операции высокого уровня DemyStified

Вы хотите скопировать, перемещать, удалять или архивировать данные со своими программами Python? Если это так, вы в нужном месте, потому что эта статья все о модуле, который был специально разработан для работы. Он называется PULTIL (короткий для утилит Shell), и мы будем демистифицировать свои ключевые функции в течение нескольких простых … Python Cutil: Высокоуровневые файловые операции Demystified Подробнее »

Автор оригинала: Jonathan Boland.

Вы хотите скопировать, перемещать, удалять или архивировать данные со своими программами Python? Если это так, вы в нужном месте, потому что эта статья все о модуле, который был специально разработан для работы. Это называется PULTIL (короткий для утилитов Shell), и мы будем демистифицировать свои ключевые функции в течение нескольких простых примеров. Мы также увидим, как использовать PULTIL в сочетании с некоторыми другими стандартными библиотечными модулями и охватываю несколько ограничений, которые могут привести к вам немного головной боли в зависимости от ваших приоритетов, используемая вами операционная система и ваша версия Python.

Слово о путях файлов

Прежде чем мы начнем, стоит упомянуть, что пути построены по-разному в зависимости от вашей операционной системы. На Mac и Linux они разделены вперед, скользями (известный как стиль POSIX) и в Windows по обратным клеям.

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

Тот факт, что пути Windows используют обратные язвы, также приводят к другому осложнению, потому что они имеют особое значение в Python. Они используются в рамках специальных символов и для сбежания, которые вы можете прочитать все в этой статье Backslash Finxter.

Поэтому вы заметите букву «R» до строк в кодовых фрагментах – этот префикс означает необработанную строку, в которой обратные косания обрабатываются как буквальные, а не специальные символы. Другим способом справиться с этой проблемой является использование второго обратной косочетания для сначала, который является форматом Python, используемый для отображения пути Windows нового файла, который был создан.

Кроме того, при использовании пути в ваших реальных программах я настоятельно рекомендую определить их с Pathlib. Путь () Отказ Если сделано правильно, это имеет эффект нормализации путей, чтобы они работали независимо от операционной системы, на которой работает программа.

Каталог PUSTIL и файловые операции

COPULT COPY

Итак, давайте выключаем все с помощью простого примера того, как скопировать один файл из одной папки в другую.

Нет необходимости устанавливать ничего, потому что забивки находится в стандартной библиотеке Python; Просто импортируйте модуль, и вы готовы к работе:

 >>> import shutil
 >>> source = r'C:\src_folder\blueprint.jpg'
 >>> destination = r'C:\dst_folder'
 >>> shutil.copy(source, destination)
 
 'C:\\dst_folder\\blueprint.jpg'

PULTIL.COPY () Располагает дубликат указанного исходного файла в папке назначения, которую вы определены, и Python подтверждает путь к файлу. Разрешения файла копируются вместе с данными. Другой вариант – указать пункт назначения Файл вместо пункта назначения папка :

 ...
 >>> source = r'C:\src_folder\blueprint.jpg'
 >>> destination = r'C:\dst_folder\plan.jpg'
 >>> shutil.copy(source, destination)
 
 'C:\\dst_folder\\plan.jpg'

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

Предупреждение: Независимо от того, копируете ли вы файл непосредственно в папку, сохраняющую его существующее имя или предоставляете имя файла назначения, если файл уже существует в папке назначения с этим именем Копировать () Будет ли постоянно перезаписать это Без предупреждения вы сначала Отказ

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

PULTIL COPY2.

copy2 () Работает так же, как Копировать () За исключением того, что в дополнение к разрешениям файла он также пытается сохранить метаданные, такие как в последний раз, когда файл был изменен.

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

Шутри Хотри

Если копирование файлов One-by-On не собирается его разрезать, copytree () это путь к работе.

 ...
 >>> source = r'C:\src_folder\directory'
 >>> destination = r'C:\dst_folder\directory_copy'
 >>> shutil.copytree(source, destination)
 
 'C:\\dst_folder\\directory_copy'

copytree () Создает дубликат всего исходного каталога и дает ему имя, которое вы указываете в пути назначения. Использует copy2 () Чтобы скопировать файлы по умолчанию, поэтому попытается сохранить метаданные, но это может быть переопределено, установив параметр copy_function. В отличие от копирования индивидуальных файлов, если каталог с тем же именем уже существует в этом пункте назначения (в этом случае Directory_Copy ), будет повышена ошибка, и дерево каталогов не будет скопировано. Итак, при попытке завершить одну и ту же операцию подператов во второй раз, это сокращенная версия того, что мы видим:

 ...
 FileExistsError: [WinError 183] Cannot create a file when that file already  
 exists: 'C:\\dst_folder\\directory_copy'

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

Если замена существующего каталога – это то, что вы хотите сделать новый вариант, был введен в Python 3.8, что делает это возможным:

 ...
 >>> shutil.copytree(source, destination, dirs_exist_ok=True)
 
 'C:\\dst_folder\\directory_copy'

dirs_exist_ok Параметр установлен на ложь по умолчанию, но изменение его в True переопределяет обычное поведение и позволяет нам завершить наше copytree () Операция во второй раз, хотя Directory_Copy уже существует в указанном месте. Еще одна удобная функция – это параметр игнорирования:

 from shutil import copytree, ignore_patterns
 
 >>> src = r'C:\src_folder\another_directory'
 >>> dst = r'C:\dst_folder\another_directory_copy'
 >>> shutil.copytree(src, dst, ignore=ignore_patterns('*.txt', 'discard*'))
 
 'C:\\dst_folder\\another_directory_copy'

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

Самый простой способ добиться этого – импортировать Watchil’s lightore_patterns Функция помощника, которая затем может быть передана в Pophertree’s Ignore Parameter.

lightore_patterns принимает один или несколько шаблонов в строковом формате, и любые файлы или папки, соответствующие их их, будут переданы, когда copytree () Создает новую версию каталога.

Например, в приведенном выше фрагменте кода мы пропустили два аргумента IGNORE_Patterns: '*.текст' и «Отменить *» Отказ звездочка (* Символ) действует как подстановочный знак, который соответствует нулю или более символам, поэтому эти шаблоны будут гарантировать, что copytree () Дубликаты все, кроме файлов, которые заканчиваются .txt и файлами или папками, которые начинаются с отброса. Это можно увидеть, просмотрев структуру файла другой_directory :

 C:\src_folder>tree /F
 ...
 C:.
 └───another_directory
     ├───discard_this_folder
     ├───include_this_folder
     │       discard_this_file.docx
     │       include_this_file.docx
     │       include_this_file_too.docx
     │       this_file_will_be_discarded.txt
     │       this_file_will_not_be_discarded.pdf
     │
     └───include_this_folder_too

А затем глядя на файловую структуру другого_directory_copy после того, как она будет создана за счет забивки:

C:\dst_folder>tree /F
 ...
 C:.
 └───another_directory_copy
     ├───include_this_folder
     │       include_this_file.docx
     │       include_this_file_too.docx
     │       this_file_will_not_be_discarded.pdf
     │
     └───include_this_folder_too

движение забивки

Переместить () работает аналогичным образом copy2 () Но позволяет передавать файл в другое место вместо того, чтобы копировать его.

Вы также можете переместить весь каталог, указав папку для его размещения:

 import shutil
 
 
 >>> source = r'C:\src_folder\diagrams'
 >>> destination = r'C:\dst_folder'
 >>> shutil.move(source, destination)
 
 'C:\\dst_folder\\diagrams'

В качестве альтернативы вы можете предоставить новое имя для каталога как часть процесса:

 ...
 >>> source = r'C:\src_folder\diagrams'
 >>> destination = r'C:\dst_folder\layouts'
 >>> shutil.move(source, destination)
 
 'C:\\dst_folder\\layouts'

В отличие от Копировать () и copy2 () , Переместить () Поднимет исключение, если файл с тем же именем уже существует в данной папке (если это не на текущей файловой системе). Такое поведение также можно наблюдать при перемещении каталогов. Переведив каталог наших диаграмм и переименовал его на макеты, если мы сейчас стараемся переместить другой каталог, называемый макетами в то же место, мы увидим следующее:

...
 >>> source = r'C:\src_folder\layouts'
 >>> destination = r'C:\dst_folder'
 >>> shutil.move(source, destination) 
 ...
 shutil.Error: Destination path 'C:\dst_folder\layouts' already exists
 

ПРЕДУПРЕЖДЕНИЕ. Однако, как и с функциями копирования при перемещении отдельных файлов, если вы включите имя файла назначения и файл с этим именем уже существует в папке назначения, Переместить () Будет ли навсегда перезаписать его без предупреждения, сначала :

...
 >>> source = r'C:\src_folder\sketch.jpg'
 >>> destination = r'C:\dst_folder\design.jpg'
 >>> shutil.move(source, destination)
 
 'C:\\dst_folder\\design.jpg'
 
 >>> source = r'C:\src_folder\different_sketch.jpg'
 >>> destination = r'C:\dst_folder\design.jpg'
 >>> shutil.move(source, destination)
 
 'C:\\dst_folder\\design.jpg'

Есть еще одна тонкая жатча, чтобы посмотреть при использовании перемещения (), который может также вызвать проблемы:

...
 >>> source = r'C:\src_folder\blueprint.jpg'
 >>> destination = r'C:\dst_folder\plan'
 >>> shutil.move(source, destination)
 
 'C:\\dst_folder\\plan'

По этому случаю мы попытались перенести файл в папку, которая не существует. Вместо того, чтобы повысить исключение, Переместить () завершил операцию и учитывая файл имя несуществующего каталога (плана) Без расширения файла Отказ Файл все еще находится в формате JPEG, но его не будет называться то, что мы ожидаем, и файловая система больше не будет распознавать его!

Такую же проблему может произойти, если мы случайно пропустили расширение файла из имени файла назначения.

Эта проблема может также обрезать при использовании функций копирования, если вы не осторожны. В этом случае вы хотя бы имели исходный файл для справки, но он все еще может привести к значительной путанице.

Закрыть Rmtree

Если вы хотите удалить весь каталог вместо перемещения или копирования его, вы можете сделать это с rmtree () :

 
 import shutil
 >>> shutil.rmtree(r'C:\dst_folder\directory_copy')

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

 ...
 PermissionError: [WinError 32] The process cannot access the file because 
 it is being used by another process: 
 'C:\\dst_folder\\directory_copy\\blueprint.pdf'

Однако это поведение может быть переопределено:

 ...
 >>> shutil.rmtree(r'C:\dst_folder\directory_copy', ignore_errors=True)

Если вы устанавливаете параметр Ignore_Errors в True, Rmtree () будет продолжать удалять каталог вместо поднятия исключения.

Предупреждение: Деревья каталогов удалены Rmtree () постоянно удалены, поэтому вам нужно быть очень осторожным о том, как вы его используете. Если вы беспокоитесь по потенциальным рискам (и я не виню вас, если бы вы были!), Вы могли бы захотеть рассмотреть безопасную альтернативу, такую как Send2trash. .

Шупитальный архив

Вы можете использовать PULTIL для создания архивов каталогов, а также:

 ...
 >>> shutil.make_archive(
         r'C:\dst_folder\zipped_designs', 
         'zip', 
         r'C:\src_folder\designs',
         )
 
 'C:\\dst_folder\\zipped_designs.zip'

Как показано выше, простой способ сделать это, передает три аргумента в функцию make_archive ():

  1. Путь, где должен быть создан новый архив, включая его имя, но без Расширение файла.
  2. Формат архива для использования при его создании.
  3. Путь каталога для заархивирования.

Каталог будет оставаться неизменным в своем исходном месте, а архив будет создан в указанном месте.

make_archive () также может создавать архивы в формате .tar, .gztar, .btar или .xztar.

Для операций более сложный, чем архивирование всего каталога, например, Zaubing выбранные файлы из каталога на основе фильтров, вы можете использовать модуль zipfile вместо.

Ограничения забивки

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

Отсутствующие файловые метаданные

copy2 () сохраняет как можно больше метаданных и используется copytree () и перемещением (), так что по умолчанию эти методы сделают то же самое. Это не может захватить все, хотя.

В Windows: владельцы файлов, списки контроля доступа (ACLS) и альтернативные потоки данных не копируются.

Владельцы файлов и ACL также теряются на Linux и Mac вместе с группами.

На Mac OS Fork Resource и другие метаданные также не используются, приводящие к потере данных ресурсов и неправильного создателя и кодов типов файлов.

Скорость

Жалоба, часто выравнивалась в забивке в прошлом, заключалась в том, что она может быть очень медленной использовать при работе с большим количеством данных, особенно на Windows.

К счастью, это было адресовано в Python 3.8 с введением Snappily под названием «Заизыванные зависимые платформы эффективной копии».

Эта «быстрая копия» означает, что копирование забивки и операции перемещения теперь оптимизированы, чтобы произойти в соответствующем Ядро операционной системы вместо буферов пользователей Python, когда это возможно.

Следовательно, если вы работаете в вопросах скорости на более ранней версии Python и используете 3,8 вместо этого, это вариант, вероятно, значительно улучшит вопросы.

Вы также можете посмотреть на сторонние пакеты, такие как Pyfastcopy Отказ

Сочетание забивки с другими стандартными библиотечными модулями

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

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

Использование Putil в сочетании с некоторыми из других стандартных библиотечных библиотечных библиотеки Python.

Этот раздел предназначен для обеспечения примера одного случая использования для этого вида подхода.

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

Для этого мы будем использовать PULTIL.Move () вместе с несколькими другими удобными модулями, включая: pathlib (который я упомянул в начале), ОС и время.

Модули

Кроме того, чтобы сделать его намного проще для определения перекрестных платформы, совместимые пути, класс PathLib содержит методы, которые действительно помогают с Обработка путей файлов эффективно Отказ

Мы также будем использовать Функция Walk Module OS , который не имеет эквивалента в Pathlib. Это позволит нам пройти наши подкаталогии, чтобы определить все файлы, которые они содержат и извлекают свои пути.

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

Подготовка к движу

Импортировав наши модули:

 import os
 import pathlib
 import shutil
 import time

Первое, что нам нужно сделать, это назначить нормальное количество секунд в год до постоянного:

SECONDS = 365 * 24 * 60 * 60

Это поможет нам определить, сколько времени прошло, поскольку файлы в наших подпапках были последнеми измененными (подробнее об этом позже).

Далее мы определяем нашу первую функцию, которая подготовит файловые операции, которые необходимы для завершения движения:

 ...
 def prepare_move(number, path, storage_folder):
     pass

Наша функция занимает три аргумента:

  1. Номер – количество лет, поскольку любой файл в подпапке был последним модифицированным (это также может быть Float такие как 1,5).
  2. PATH – путь к файлу главного каталога, который содержит подкаталоги, которые мы хотим принять.
  3. Storage_Folder – имя папки, в которой мы хотим, чтобы старые каталоги были размещены. После завершения работы эта папка хранения будет помещена в основной каталог наряду с подкатариатами, которые не были перемещены.

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

 ...
 def prepare_move(number, path, storage_folder):
     length = SECONDS * number
     now = time.time()
     my_directory = pathlib.Path(path)
     my_subdirectories = (item for item in my_directory.iterdir() if item.is_dir())
  1. Длина – это результат умножения постоянных секунд, которые мы ранее определяли количество лет, переданных в функцию.
  2. Теперь – это текущее время в секундах, предусмотренных модулем времени. Это рассчитывается на основе того, что известно как эпоха Отказ
  3. my_directory – хранит основной путь каталога, который мы передали функции как pathlib. Объект пути.
  4. my_subdireteries – это Генератор Содержащие пути наших подкаталогов, произведенных итерацией через My_Directory.

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

 ...
 def prepare_move(number, path, storage_folder):
     length = SECONDS * number
     now = time.time()
     my_directory = pathlib.Path(path)
     my_subdirectories = (item for item in my_directory.iterdir() if item.is_dir())
     file_operations = []
     for subdirectory in my_subdirectories:
         time_stats = _get_stats(subdirectory)

Первая задача, выполняемая циклом, состоит в том, чтобы создать список всех модифицированных времен файла в подкаталоге.

Это обрабатывается отдельной функцией, которая использует упоминание метода прогулки ОС ранее и последнее модифицированное значение в секундах (ST_MTime) доступно через Path.Stat () Утилита:

 ...
 def _get_stats(subdirectory):
     time_stats = []
     for folder, _, files in os.walk(subdirectory):
         for file in files:
             file_path = pathlib.Path (folder) / file
             time_stat = file_path.stat().st_mtime
             time_stats.append(time_stat)
     return time_stats

Затем LOOP проверяет эти файлы модифицированные статистики, чтобы увидеть, все ли они предшествуют указанному пункту во времени (с вычислением, выполняемым в секундах).

Если это так, необходимые пути источника и назначения построены и добавлены к списку файлов_Operations.

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

 ...
 def prepare_move(number, path, storage_folder):
     length = SECONDS * number
     now = time.time()
     my_directory = pathlib.Path(path)
     my_subdirectories = (item for item in my_directory.iterdir() if item.is_dir())
     file_operations = []
     for subdirectory in my_subdirectories:
         time_stats = _get_stats(subdirectory)
         if all(time_stat < (now - length) for time_stat in time_stats):
             *_, subdirectory_name = subdirectory.parts
             source = subdirectory
             destination = my_directory / storage_folder / subdirectory_name
             file_operations.append((source, destination))
     return file_operations

Перемещение подкаталогии

Теперь нам нужно определить функцию, которая фактически переместит файл:

 ...
 def move_files(file_operations):
     for operation in file_operations:
         source, destination = operation
         shutil.move(source, destination)

Поскольку все готовые работы уже выполнены, эта функция просто принимает файловые операции и передает их в PURTIL.Move () через A для LOOP, чтобы каждый старый подкаталог можно разместить в указанном Storage_Folder.

Выполнение программы

Наконец, мы определяем Главная () Функция для выполнения программы и вызовите ее с нашими аргументами:

 ...
 def main(number, path, storage_folder):
     file_operations = prepare_move(number, path, storage_folder)
     move_files(file_operations)
 
 main(1, r"F:\my_directory", "old_stuff")

Вот вся программа:

 
 import os
 import pathlib
 import shutil
 import time
 
 
 SECONDS = 365 * 24 * 60 * 60
 
 
 def prepare_move(number, path, storage_folder):
     length = SECONDS * number
     now = time.time()
     my_directory = pathlib.Path(path)
     my_subdirectories = (item for item in my_directory.iterdir() if item.is_dir())
     file_operations = []
     for subdirectory in my_subdirectories:
         time_stats = _get_stats(subdirectory)
         if all(time_stat < (now - length) for time_stat in time_stats):
             *_, subdirectory_name = subdirectory.parts
             source = subdirectory
             destination = my_directory / storage_folder / subdirectory_name
             file_operations.append((source, destination))
     return file_operations
 
 
 def _get_stats(subdirectory):
     time_stats = []
     for folder, _, files in os.walk(subdirectory):
         for file in files:
             file_path = pathlib.Path (folder) / file
             time_stat = file_path.stat().st_mtime
             time_stats.append(time_stat)
     return time_stats
 
 
 def move_files(file_operations):
     for operation in file_operations:
         source, destination = operation
         shutil.move(source, destination)
 
 
 def main(number, path, storage_folder):
     file_operations = prepare_move(number, path, storage_folder)
     move_files(file_operations)
 
 main(1, r"F:\my_directory", "old_stuff")

Вы можете увидеть, как структура каталогов выглядела, прежде чем запустить программу ниже:

 F:\my_directory>tree /F
 ...
 F:.
 ├───new_files_1
 │   │   new_file.jpg
 │   │
 │   ├───second_level_folder_1
 │   │       really_new_file.txt
 │   │
 │   └───second_level_folder_2
 │           very_new_file.txt
 │
 ├───new_files_2
 │       fairly_new_file.txt
 │
 ├───old_files_1
 │   │   old_file.txt
 │   │
 │   └───second_level_folder_1
 │       │   old_file_as_well.txt
 │       │
 │       └───third_level_folder
 │               really_old_file.jpg
 │
 └───old_files_2
     │   another_old_file.txt
     │
     └───old_second_level_folder
             oldest_file.jpg
             old_file_2.txt

И это то, что он выглядит потом:

 
 F:\my_directory>tree /F
 ...
 F:.
  ├───new_files_1
  │   │   new_file.jpg
  │   │
  │   ├───second_level_folder_1
  │   │       really_new_file.txt
  │   │
  │   └───second_level_folder_2
  │           very_new_file.txt
  │
  ├───new_files_2
  │       fairly_new_file.txt
  │
  └───old_stuff
      ├───old_files_1
      │   │   old_file.txt
      │   │
      │   └───second_level_folder_1
      │       │   old_file_as_well.txt
      │       │
      │       └───third_level_folder
      │               really_old_file.jpg
      │
      └───old_files_2
          │   another_old_file.txt
          │
          └───old_second_level_folder
                  oldest_file.jpg
                  old_file_2.txt 

Очевидно, что если у вас был каталог этот маленький или один, где все подкаталоги были помечены как старые, либо новые, вы вряд ли нуждаетесь в такой программе! Но, надеюсь, этот основной пример помогает иллюстрировать, как процесс будет работать с большей, менее интуитивно понятным каталогом.

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

Последние мысли

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

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

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

Оригинал: “https://blog.finxter.com/python-shutil-high-level-file-operations-demystified/”