Доброго времени суток, читатели, зрители моего канала programmer's notes. Не забывайте подписываться и писать свои комментарии к моим статьям и видео.
Копирование файлов и функции обратного вызова
Сегодня довольно объёмный материал, не столько по тексту, сколько по тому, какие области программирования на Python мы будем использовать. Ну и для слегка продвинутых в Python'е людей.
Ну новым здесь будет функции обратного вызова (callback functions). Суть этих функций очень проста. Если вы используете какую-то библиотечную функцию, которая выполняет некоторое, возможно, продолжительное действие, то часто предлагается ввести ссылку на функцию, которая будет вызвана, если произойдёт какое то событие или будет произведено какое-то действие. Может быть это будет ошибка или функция будет вызываться после получения очередной порции данных или что-то ещё. Если вы укажите свою функцию, то при наступлении какого-то события будет вызван ваш код. Другими словами, вы встраиваете свой код в код некоторой библиотечной функции. Ниже мы об этом механизме поговорим подробнее.
Кроме того нами будет использованы знания из области объектно-ориентированного программирования. У меня на канале много статей по ООП.
И еще одна серьёзная тема, это управление файлами. Основные операции с ними. Подборка статей по этой теме здесь. Нужно посмотреть статьи по работе с бинарными файлами: открыть, закрыть, читать, записать.
Наконец, в нашей библиотеке создаётся таймер (модуль Timer) посредством библиотеки threading. Т.е. мы используем ещё и элементы многозадачности. Непосредственно же по таймеру можно посмотреть статью здесь.
А задача, которую мы будем рассматривать, это копирование файла. При это функция копирования предполагает возможность переопределения функций обратного вызова: при возникновении ошибки, если не удалось открыть файлы и периодический вызов с возможностью проверки того, каков прогресс копирования (если файл большой, конечно).
Ниже (см. Рисунок 1), представлена библиотека копирования. Класс copyb и функция copy_f(), через которую и осуществляется доступ к возможностям класс.
Разъяснение к программному коду, представленному на рисунке 1
1. Доступ к возможностям класса осуществляется через функцию copy_f() и её параметры.
2. Прежде всего определимся с параметрами функции copy_f(). Первый и второй параметр это это имя файла, который мы будем копировать и имя файла, куда мы будем копировать. Далее именованные параметры: mess - имя функции, которая будет вызываться с указанным далее периодом для контроля процесса копирования, err - имя функции, которая будет вызвана в случае если при открытии файлов произошла ошибка, nb - задать размер буфера для копирования, tm - период вызова функции, указанной параметром mess, если это параметр не None.
3. В функции copy_f() создаётся объект класса copyb. После этого появляются возможности использовать свойства и методы класса. Во-первых, если указан параметр err, то определяется функция, которая будет вызвана, если произойдёт ошибка открытия файлов. Во-вторых, если указан параметр nb, то определяется размер буфера, который используется при копировании. В третьих, если определён параметр mess, запускается таймер, с указанием периода его вызова. После всего запускается метод copy(), который и производит копирование.
4. Обратимся теперь к таймеру. Как я уже сказал, он запускается, когда параметр mess не равен None, т.е. является именем существующей функции, она то и будет одной из функций обратного вызова, которая будет вызываться через указанный промежуток. Смотрим метод start(). Ну, во-первых сразу обращу внимание на свойство self.tim. Свойство равно 1, если таймер работает. Таймер прекратит работу, если изменить значение на 0. Таймер создаётся, как объект библиотеки threading. При создании мы указываем три параметра: период вызова, функция, которая будет вызвана, список параметров, которые будут переданы вызываемой функции. Ну, а запуск таймера осуществляется методом start(). С этого момента начинается отсчёт времени. Запуск будет единичным, так что придётся снова и снова повторить процесс создания и запуска таймера. Тут важно отметить следующее: функция обратного вызова для таймера должна содержать важный код, о котором я скажу ниже. Тогда всё будет работать корректно.
5. Обратимся теперь методу copy(), который и осуществляет копирование. Рассмотрим в начале две команды:
f1 = open(ffr, 'rb')
f2 = open(fto, 'wb')
Один файл открываем для чтения, другой для записи. Выше я указал ссылку, если вы забыли работу с файлами. Но могу указать ещё раз: здесь. Обращу внимание, что код находится в блоке try. Если ошибка произойдёт при открытии, то управление сразу будет передано в блок except. Важный нюанс. Почему я написал открытие в две строки, а не скажем так
f1, f2 = open(ffr, 'rb'), open(fto, 'wb'), что несколько сократило бы код. Написание в две строки позволяет нам точно определить какой файл был открыт, а какой нет, т.е. на каком файле ошибка произошла.
6. Продолжим по поводу ошибок при открытии файлов. Т.е. рассмотрим блок except. Внимательно на него глянем. Ну, во-первых, переменной self.tim мы присваиваем 0, т.е. отменяем таймер, ну если он был запущен. Ну, а если нет? Ну на нет и суда нет. Ну а далее мы определяем был ли открыт файл и его закрываем. Выражение типа 'f1' in locals() равно True если локальная переменная существует. Кажется я эту функцию нигде раньше не использовал на канале. Она возвращает словарик по именам локальных переменных. Наконец, в конце блока мы запускаем self.er(). дело в том, что в классе copyb такой метод есть, но он пустой. Это просто заглушка. Если же при запуске функции copy_f() мы указали параметр err, то происходит подмена и теперь self.er указывает на вашу функцию. Это тоже функция обратного вызова, но она определяется другим способом нежели функция, вызываемая по таймеру.
7. После открытия файлов, копирование не представляет сложности. Читаем в буфер и пишем в файл, открытый для записи. Если при очередной записи будет записано меньше, чем размер буфера, значит это последняя запись. Обращаю внимание на свойства, которые могут пригодится, если вы будете обрабатывать вызов таймера: self.n - размер буфера, self.nn - длина копируемого файла, self.ll - количество скопированный байтов на данный момент. По поводу того как определить длину файла. f1.seek(0, 2) - перемещает указатель в конец файла и возвращает его длину, f1.seek(0, 0) - указатель возвращается в начало.
8. Важные строки
f2.flush()
os.sync()
они нужны, если мы будем обрабатывать таймер, выводя данные копирования. Это синхронизирует все буферы. В противном случае вывод данных будет не всегда синхронизирован. Но использование данных строк замедляет копирование.
9. После окончания копирования следует закрыть файлы и выключить таймер, иначе он так и будет работать. Для этого используется метод Timer.cancel(). Кстати обращу ваше внимание на один важный момент. Если произойдёт ошибка при открытии файлов, а задали ещё и функцию обработку файлов, то таймер один раз всё равно должен отработать и в функции обработки вызовов файлов нужно это учесть и проверить свойство tim. Посмотрите выше его смысл. Ниже мы это рассмотрим.
И так я разобрал класс и функцию copy_f(). Теперь посмотрим пример вызова копирования с функциями обратного вызова. Фрагмент этого кода представлен на рисунке 2.
Фрагмент на рисунке 2 предполагает, что код находится в том же модуле, что и библиотека. Как вызвать функцию, если библиотека находится в другом модуле, вы знаете.
Копируем файл, определяемый путём s1 в файл, определяемый путём s2. Функция обратного вызова для обработки ошибок открытия файлов просто выводит сообщение. См. функцию. er1(). А вот функция mes1() куда более интересна. Она вызывается по таймеру. Что означает *tm? Значит параметры отправлены списком. Для этого смотрим метод start(), мы его уже рассматривали, но гляньте ещё раз. И так tm[0] - объект класса copyb, tm[1] - имя функции обратного вызова, tm[2] - длина периода таймера. И очень важная (обязательная) строка if tm[0].tim == 0: - т.е. если мы указали, что переменная tim=0 при ошибке открытия файлов, таймер больше не запустится (return). Эта обработка обязательна, иначе таймер не отключится.
На рисунке 3 пример вывода программы при копировании большого файла.
Замечание
Текст и так получился приличный, поэтому вынесу это замечание отдельно. Обработка исключений. Конечно, ошибка могут возникнуть не только при открытии файла, но при его копировании. Просто вероятность меньше, поэтому я и говорю об об ошибках открытия файлов.
Ну, пока всё!
Пишите свои предложения и замечания, и занимайтесь программированием, а также проектированием баз данных, хотя бы для поддержания уровня интеллекта.