При работе над задачами системного уровня необходимо регулярно применять одно и то же действие ко всем (Python) файлам одного типа: архивирование, подсчет количества строк, удаление файлов журнала старше 30 дней и т. д…
Содержание
Просмотр записей в каталоге
listdir
Пакет ‘os’ предоставляет функцию ‘listdir’, которая для заданного имени каталога возвращает список имен файлов/каталогов, находящихся в нем.
Вот пример использования :
from os import listdir
for name in listdir("C:\\Temp"):
print(name)
Code language: JavaScript (javascript)
walk
walk – это итератор, который позволяет пользователю избежать рекурсивного просмотра каталога.
from os import walk
for root, dirs, files in walk('C:\\Temp', topdown=True):
print(root)
print(dirs)
print(files)
print('--------------------------------')
Code language: PHP (php)
Эта функция выделяет множество списков строк.
scandir
Начиная с Python 3.4, рекомендуемым методом является ‘scandir’, поскольку он гораздо более эффективен. Рост производительности может достигать 20 раз.
from os import scandir
with scandir("C:\\Temp") as it:
for entry in it:
print(entry.name)
Code language: JavaScript (javascript)
Контекстуальное использование с ‘with’ является важным, оно гарантирует освобождение открытых ресурсов.
Итератор, полученный с помощью ‘scandir’, позволяет просматривать экземпляры объекта типа os.DirEntry, благодаря которому мы можем получить доступ к различным функциям/свойствам, позволяющим узнать характер входных данных (файл, каталог или символическая ссылка) или такую информацию, как даты и размеры.
Повторное использование
Идея заключается в том, чтобы создать функцию ‘filemap’, которая будет рекурсивно просматривать дерево и применять действие ко всем файлам, которые являются целью.
Затем мы можем повторно использовать эту функцию в различных контекстах…
from os import scandir
def filemap(path, action, is_target):
with scandir(path) as it:
for entry in it:
if entry.is_dir():
filemap(entry.path, action, is_target)
elif is_target(entry):
action(entry)
Code language: JavaScript (javascript)
Параметр ‘action’ используется для передачи callback функции, которая принимает на вход экземпляр объекта os.DirEntry.
Эта функция применит действие (удалить, переместить и т.д…) к файлу.
Вот пример использования:
filemap("c:\\Temp",
lambda e: print(e.name),
lambda e: e.name.lower().endswith(".py"))
Code language: CSS (css)
Мы отображаем имя каждого исходного файла Python, присутствующего в дереве “C:\Temp”.
Подсчет файлов
Подсчет файлов – это необходимость, которая интересна с функциональной точки зрения (проверка того, что количество файлов, создаваемых процессом, совпадает с ожидаемым), а также с вычислительной точки зрения, поскольку это позволит нам лучше продумать наш код.
Самая очевидная реализация была бы такой: :
count = 0
def count_file(entry):
global count
count += 1
filemap("c:\\Temp",
count_file,
lambda e: e.name.lower().endswith(".py"))
print(count)
Code language: PHP (php)
count_file’ действительно считает файлы, но это не соответствует концепции чистоты функционального программирования, поскольку зависит от глобальной переменной.
Если бы я забыл сбросить ‘count’ между двумя вызовами ‘filemap’, я бы получил неправильный результат.
Чтобы исправить эту проблему, в Python есть очень интересный инструмент: мы можем создавать объекты, которые ведут себя как функции.
Благодаря объекту мы можем хранить состояние счетчика, сохраняя его чистоту…
class CountFile: def __init__(self): self.__count = 0 @property def count(self): return self.__count def __call__(self, entry): self.__count += 1 def __str__(self): return F"{self.__count}" c = CountFile() filemap("c:\\Temp", c, lambda e : e.name.lower().endswith( ".py" )) print(c)
__call__ – это действие, которое вызывается и позволяет объекту вести себя как функция.
Таргетирование
Таргетинг по расширениям
В нашем предыдущем примере мы нацелились на исходные файлы Python с помощью лямбда-функции
lambda e: e.name.lower().endswith(".py")
Code language: CSS (css)
Регулярно возникает необходимость искать файлы по их расширению.
Нам нужен более простой и быстрый способ таргетироваться на тип расширения или группу расширений.
Итоговое решение :
def endswith(*args):
extensions = [ext.lower() for ext in args]
def _is_target(entry):
name = entry.name.lower()
for ext in extensions:
if name.endswith(ext):
return True
return False
return _is_target
c = CountFile()
filemap("C:\\Tools\\Anaconda3",
c,
endswith(".py", ".txt"))
print(c)
Code language: PHP (php)
Таргетинг по дате создания
При управлении рабочими директориями нам часто приходится настраивать инструменты для удаления файлов в соответствии с датами их создания: мы удаляем файлы, созданные более 30 дней назад.
def older_than(day_count):
from datetime import datetime as DT,
timedelta, timezone
today = DT.now()
epoch = DT(1970, 1, 1)
ref = timedelta(hours = day_count * 24)
def _is_target(entry):
creation_date = epoch + timedelta(seconds = entry.stat().st_ctime)
return (today - creation_date) >= ref
return _is_target
filemap("C:\\Temp",
lambda e: unlink( e.path ),
older_than(30))
Code language: JavaScript (javascript)