И как же можно было бы написать такую обертку интерфейса FTP (задаст читатель риторический вопрос)? При наличии библиотечного модуля ftplib создать обертку для загрузки конкретного файла из конкретного каталога достаточно просто. Объекты соединений FTP поддерживают два метода загрузки:
retrbinary
Этот метод загружает запрашиваемый файл в двоичном режиме, блоками посылая его байты указанной функции, без преобразования символов конца строки. Обычно в качестве функции указывается метод write объекта открытого локального файла, благодаря которому байты помещаются в локальный файл на стороне клиента.
retrlines
Этот метод загружает запрашиваемый файл в режиме текста ASCII, посылая заданной функции строки текста с удаленными символами конца строки. Обычно указанная функция добавляет символ новой строки \n (преобразуемый в зависимости от платформы клиента) и записывает строку в локальный файл.
Позднее мы встретимся с примером использования метода retrlines — вспомогательный модуль getfile в примере 13.4 всегда осуществляет передачу в двоичном режиме с помощью метода retrbinary. Это означает, что файлы загружаются в точности в том виде, в каком они находятся на сервере, байт в байт, сохраняя для текстовых файлов те символы конца строки, которые приняты на сервере (если эти символы выглядят необычно в вашем текстовом редакторе, может потребоваться преобразовать их после загрузки — указания смотрите в справке к своему текстовому редактору или к командной оболочке или напишите сценарий Python, который открывал бы и записывал текст так, как необходимо).
Пример 13.4. PP4E\Internet\Ftp\getfile.py
#!/usr/local/bin/python """
Загружает произвольный файл по FTP. Используется анонимный доступ к FTP, если не указан кортеж user=(имя, пароль). В разделе самопроверки используются тестовый FTP-сайт и файл.
from ftplib import FTP # инструменты FTP на основе сокетов
from os.path import exists # проверка наличия файла
def getfile(file, site, dir, user=(), *, verbose=True, refetch=False)
загружает файл по ftp с сайта/каталога, используя анонимный доступ или действительную учетную запись, двоичный режим передачи if exists(file) and not refetch:
if verbose: print(file, ‘already fetched’) else:
if verbose: print(‘Downloading’, file)
local = open(file, ‘wb‘) # локальный файл с тем же именем try:
remote = FTP(site) # соединиться с FTP-сайтом remote.login(*user) # для анонимного =() или (имя, пароль) remote.cwd(dir)
remote.retrbinary(‘RETR ‘ + file, local.write, 1024) remote.quit()
finally:
local.close() # закрыть файл в любом случае
if verbose: print(‘Download done.’) # исключения обрабатывает
# вызывающая программа
if __name__ == ‘__main__’:
from getpass import getpass
file = ‘monkeys.jpg’ dir = ‘.’
site = ‘ftp.rmi.net’
user = (‘lutz’, getpass(‘Pswd?’)) getfile(file, site, dir, user)
Этот модуль, по сути, просто придает иную форму программному коду FTP, использовавшемуся выше для получения файла изображения, с целью сделать его более простым и многократно используемым. Так как экспортируемая здесь функция getfile.getfile является вызываемой, она стремится быть максимально надежной и широко используемой, но даже такая маленькая функция требует некоторых конструктивных решений. Вот несколько замечаний по использованию:
Режим FTP
Функция getfile в этом сценарии по умолчанию использует анонимный режим доступа по FTP, однако имеется возможность передать в аргументе user кортеж из двух элементов с именем пользователя и паролем, чтобы зарегистрироваться на удаленном сервере в неанонимном режиме. Для работы по FTP в анонимном режиме не передавайте этот аргумент или передайте в нем пустой кортеж (). Метод login объекта FTP принимает два необязательных аргумента, обозначающие имя пользователя и пароль, а синтаксис вызова functi— on(*args), используемый в примере 13.4, отправляет ему тот кортеж, который был передан в аргументе user, в виде отдельных аргументов.
Режимы обработки
Последние два аргумента (verbose, refetch) позволяют отключить сообщения о состоянии, выводимые в поток stdout (возможно, нежелательные в контексте графического интерфейса), и принудительно выполнить загрузку, даже если локальный файл уже существует (загрузка перезаписывает существующий файл).
Эти два аргумента оформлены как аргументы со значениями по умолчанию, которые могут передаваться толь ко как име но ван ные ар гумен ты в Python 3.X, поэтому при использовании они должны передаваться по имени, а не по позиции. Аргумент user, напротив, может передаваться любым способом, если он вообще передается. Передача этих аргументов только в виде именованных предотвращает ошибочное сопоставление значения verbose или refetch с аргументом user, если он отсутствует в вызове функции.
Протокол обработ ки исключений
Предполагается, что исключения будут обрабатываться вызывающей программой. Данная функция заключает загрузку в оператор try/finally, чтобы гарантировать закрытие локального выходного файла, но разрешает дальнейшее распространение исключения. Например, при использовании в графическом интерфейсе или при вызове в отдельном потоке выполнения исключения могут потребовать особой обработки, о которой этот модуль ничего не знает.
Самотестирование
Когда этот модуль запускается как самостоятельный сценарий, он загружает с целью самопроверки файл изображения с моего веб-сайта (укажите здесь свой сервер и файл), но обычно этой функции передаются определенные имена файлов, сайтов и каталогов для FTP.
Режим открытия файла
Как и в предыдущих примерах, этот сценарий открывает локальный выходной файл в двоичном режиме wb, чтобы подавить преобразование символов конца строки и обеспечить соответствие модели строк Юникода в Python 3.X. Как мы узнали в главе 4, файлы с действительно двоичными данными могут содержать байты со значением \n, соответствующим символу конца строки. Открытие их в текстовом режиме w приведет к автоматическому преобразованию этих байтов в последовательность \r\n при записи в Windows локально. Эта проблема наблюдается только в Windows — режим w изменяет символы конца не во всех системах.
Однако, как мы узнали в той же главе 4, двоичный режим необходим также для подавления автоматического кодирования символов Юникода, выполняемого в Python 3.X при работе с текстовыми файлами. Если бы мы использовали текстовый файл, Python попытался бы выполнить кодирование полученных данных при записи, используя кодировку по умолчанию или указанную явно, что может приводить к ошибкам при работе с некоторыми текстовыми данными, и обычно приводит к ошибкам при работе с действительно двоичными данными, такими как изображения и аудиоданные.
Поскольку метод retrbinary в версии 3.X будет пытаться записывать строки bytes, мы в действительности просто не сможем открыть выходной файл в текстовом режиме. В противном случае метод write будет возбуждать исключение. Напомню, что текстовые файлы в Python 3.X требуют при записи передавать строки типа str, а двоичные файлы ожидают получить строки bytes. Поскольку метод retrbinary записывает строки bytes, а метод retrlines — строки str, они неявно требуют открывать выходной файл в двоичном или в текстовом режиме соответственно. Данное ограничение действует независимо от проблемы преобразования символов конца строки и кодирования символов Юникода, но его удовлетворение фактически решает и эти проблемы.
Как мы увидим в последующих примерах, операции загрузки файлов в текстовом режиме накладывают дополнительные требования, касающиеся кодировки. В действительности, модуль ftplib может служить отличным примером влияния модели строк Юникода в Python 3.X на практическую реализацию. Постоянно используя двоичный режим в этом сценарии, мы полностью уходим от этой проблемы.
Модель каталогов
Данная функция использует одно и то же имя для идентификации удаленного файла и локального файла, в котором должно быть сохранено загруженное содержимое. Поэтому ее следует выполнять в том каталоге, где должен оказаться загруженный файл. При необходимости переместиться в нужный каталог используйте os.chdir. (Можно было бы сделать так, чтобы аргумент file представлял имя локального файла, и убирать из него локальный каталог с помощью os.path.split или принимать два аргумента с именами файлов — локального и удаленного.)
Обратите также внимание, что несмотря на свое название, этот модуль значительно отличается от сценария getfile.py, рассматривавшегося в конце материала по сокетам в предыдущей главе. Основанный на сокетах модуль getfile реализовывал логику клиента и сервера для загрузки файла с сервера на компьютер клиента непосредственно через сокеты.
Этот новый модуль getfile является исключительно инструментом клиента. Для запроса файла с сервера вместо непосредственного использования сокетов в нем применяется стандартный протокол FTP. Все детали работы с сокетами скрыты в реализации протокола FTP для клиента внутри модуля ftplib. Кроме того, сервер здесь является программой, постоянно выполняемой на компьютере сервера, которая ждет запросов FTP на сокете и отвечает на них, используя выделенный порт FTP (номер 21). Таким образом, этому сценарию требуется, чтобы на компьютере, где находится нужный файл, работал сервер FTP, и весьма вероятно, такой сервер там есть.
Утилита выгрузки
Если уж мы ввязались в это дело, напишем сценарий для выгрузки (upload) по FTP одиночного файла на удаленный компьютер. Интерфейсы выгрузки на сервер в модуле реализации протокола FTP симметричны интерфейсам загрузки с сервера. Если есть подключенный объект FTP:
• Посредством его метода storbinary можно выгружать на сервер байты из открытого объекта локального файла.
• Посредством его метода storlines можно выгружать на сервер текст в режиме ASCII из открытого объекта локального файла.
В отличие от интерфейсов загрузки с сервера, обоим этим методам передается объект файла целиком, а не метод этого объекта (или другая функция). С методом storlines мы еще встретимся в более позднем примере. Вспомогательный модуль, представленный в примере 13.5, использует метод storbinary, таким образом, файл, имя которого передается методу, всегда передается дословно — в двоичном режиме, без кодирования символов Юникода или преобразования символов конца строки соответственно соглашениям, принятым на целевой платформе. Если этот сценарий выгрузит текстовый файл, он будет получен точно в том виде, в каком хранился на компьютере, откуда поступил, со всеми символами конца строки и в кодировке, используемой на стороне клиента.
Пример 13.5. PP4E\Internet\Ftp\putfile.py
#!/usr/local/bin/python """
Выгружает произвольный файл по FTP в двоичном режиме.
Использует анонимный доступ к ftp, если функции не был передан кортеж user=(имя, пароль) аргументов.
# инструменты FTP на основе сокетов
def putfile(file, site, dir, user=(), *, verbose=True): """
выгружает произвольный файл по FTP на сайт/каталог, используя анонимный доступ или действительную учетную запись, двоичный режим передачи if verbose: print(‘Uploading‘, file)
local = open(file, ‘rb‘) # локальный файл с тем же именем remote = ftplib.FTP(site) # соединиться с FTP-сайтом remote.login(*user) # анонимная или действительная учетная запись
remote.cwd(dir)
remote.storbinary(‘STOR ‘ + file, local, 1024) remote.quit() local.close()
if verbose: print(‘Upload done.’)
if __name__ == ‘__main__’: site = ‘ftp.rmi.net’ dir = ‘.’
import sys, getpass
pswd = getpass.getpass(site+’ pswd?’) # имя файла в командной строке putfile(sys.argv[1], site, dir, user=(‘lutz’, pswd)) # действительная
# учетная запись
Обратите внимание, что для переносимости локальный файл на этот раз открывается в двоичном режиме rb, чтобы предотвратить автоматическое преобразование символа конца строки. Если файл действительно является двоичным, было бы нежелательно, чтобы из него таинственным образом исчезли байты, значением которых окажется символ возврата каретки \r, когда он пересылается клиентом, выполняющимся в Windows. Нам также требуется подавить кодирование символов Юникода при передаче нетекстовых файлов и требуется читать из исходного файла строки bytes, которые ожидает получить метод storbinary, выполняющий операцию выгрузки (подробнее о режимах открытия входного файла рассказывается ниже).
Программный код самотестирования этого сценария выгружает файл, имя которого указано в командной строке, но обычно вы будете передавать ему строки действительных имен файла, сайта и каталога. Кроме того, как и в утилите загрузки файла, можно передать кортеж (имя_ поль зов а теля, па роль) в качестве аргумента user для работы в режиме неанонимного доступа (по умолчанию используется анонимный доступ).
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011