Оригинальные версии сценариев загрузки и выгрузки включают программный код верхнего уровня, который опирается на глобальные переменные. Такая структура плохо поддается повторному использованию — программный код выполняется немедленно, на этапе импортирования, и его сложно обобщить для применения в различных контекстах. Хуже того, его сложно сопровождать — всякий раз, щелкая на кнопке Paste (Копировать), чтобы скопировать фрагмент существующего программного кода, вы увеличиваете сложность внесения изменений в будущем.
Чтобы показать более удачное решение, в примере 13.12 демонстрируется один из способов ре фак торин га (реорганизации) сценария загрузки. Благодаря обертыванию различных частей в функции они становятся доступными для повторного использования в других модулях, включая и программу выгрузки.
Пример 13.12. PP4E\Internet\Ftp\Mirror\downloadflat_modular.py
#!/bin/env python """
############################################################################ использует протокол FTP для копирования (загрузки) всех файлов из каталога на удаленном сайте в каталог на локальном компьютере; эта версия действует точно так же, но была реорганизована с целью завернуть фрагменты программного кода в функции, чтобы их можно было повторно использовать в сценарии выгрузки каталога и, возможно, в других программах в будущем — в противном случае избыточность программного кода может с течением времени привести к появлению различий в изначально одинаковых фрагментах и усложнит сопровождение.
############################################################################
import os, sys, ftplib
from getpass import getpass
from mimetypes import guess_type, add_type
defaultSite = ‘home.rmi.net’
defaultRdir = ‘.’
defaultUser = ‘lutz’
def configTransfer(site=defaultSite, rdir=defaultRdir, user=defaultUser)
принимает параметры выгрузки или загрузки
из-за большого количества параметров использует класс class cf: pass
cf.nonpassive = False # пассивный режим FTP, по умолчанию в 2.1+
cf.remotesite = site # удаленный сайт куда/откуда выполняется передача
cf.remotedir = rdir # и каталог (‘.’ означает корень учетной записи)
cf.remoteuser = user
cf.localdir = (len(sys.argv) > 1 and sys.argv[1]) or ‘.’
cf.cleanall = input(‘Clean target directory first? ‘)[:1] in [‘y’,’Y’] cf.remotepass = getpass(
‘Password for %s on %s:’ % (cf.remoteuser, cf.remotesite)) return cf
def isTextKind(remotename, trace=True)
использует mimetype для определения принадлежности файла к текстовому или двоичному типу
‘f.html‘ определяется как (‘text/html‘, None): текст
‘f.jpeg‘ определяется как (‘image/jpeg‘, None): двоичный
‘f.txt.gz‘ определяется как (‘text/plain‘, ‘gzip‘): двоичный
файлы с неизвестными расширениями определяются как (None, None): двоичные
модуль mimetype способен также строить предположения об именах исходя из типа: смотрите пример PyMailGUI
add_type(‘text/x—python—win‘, ‘.pyw‘) # отсутствует в таблицах
mimetype,encoding = guess_type(remotename,strict=False) # разрешить # расширенную интерпретацию
mimetype = mimetype or ‘?/?’ # тип неизвестен?
maintype = mimetype.split(‘/’)[0] # получить первый элемент
if trace: print(maintype, encoding or »)
return maintype == ‘text’ and encoding == None # не сжатый
def connectFtp(cf):
print(‘connecting…’)
connection = ftplib.FTP(cf.remotesite) # соединиться с FTP—сайтом connection.login(cf.remoteuser, cf.remotepass) # зарегистрироваться connection.cwd(cf.remotedir) # перейти в каталог
if cf.nonpassive: # переход в активный режим FTP при необходимости
connection.set_pasv(False) # большинство работают в пассивном режиме return connection
def cleanLocals(cf):
пытается удалить все локальные файлы, чтобы убрать устаревшие копии if cf.cleanall:
for localname in os.listdir(cf.localdir): # список локальных файлов try: # удаление локального файла
print(‘deleting local’, localname)
os.remove(os.path.join(cf.localdir, localname))
except: print(‘cannot delete local’, localname)
def downloadAll(cf, connection):
загружает все файлы из удаленного каталога в соответствии с настройками в cf; метод nlst() возвращает список файлов, dir() — полный список с дополнительными подробностями
remotefiles = connection.nlst() # nlst — список файлов на сервере
for remotename in remotefiles:
if remotename in (‘.’, ‘..’): continue
localpath = os.path.join(cf.localdir, remotename)
print(‘downloading’, remotename, ‘to’, localpath, ‘as’, end=’ ‘)
if isTextKind(remotename):
# использовать текстовый режим передачи
localfile = open(localpath, ‘w’, encoding=connection.encoding)
def callback(line): localfile.write(line + ‘\n’) connection.retrlines(‘RETR ‘ + remotename, callback) else:
# использовать двоичный режим передачи
localfile = open(localpath, ‘wb’)
connection.retrbinary(‘RETR ‘ + remotename, localfile.write) localfile.close()
connection.quit()
print(‘Done:’, len(remotefiles), ‘files downloaded.’)
if __name__ == ‘__main__’:
cf = configTransfer()
conn = connectFtp(cf)
cleanLocals(cf) # не улалять файлы, если соединение не было установлено downloadAll(cf, conn)
Сравните эту версию с оригиналом. Этот сценарий и все остальные в этом разделе действуют точно так же, как и оригинальные программы загрузки и выгрузки плоского каталога. Хотя мы и не изменили поведение сценария, тем не менее, мы радикально изменили его структуру — его реализация теперь представляет собой комплект инструментов, которые можно импортировать и повторно использовать в других программах.
В примере 13.13 приводится реорганизованная версия программы выгрузки, которая теперь стала значительно проще и использует один и тот же программный код совместно со сценарием загрузки, благодаря чему изменения при необходимости потребуется вносить только в одном месте.
Пример 13.13. PP4E\Internet\Ftp\Mirror\uploadflat_modular.py
#!/bin/env python
############################################################################ использует FTP для выгрузки всех файлов из локального каталога на удаленный сайт/каталог; эта версия повторно использует функции из сценария загрузки, чтобы избежать избыточности программного кода;
############################################################################
import os
from downloadflat_modular import configTransfer, connectFtp, isTextKind
def cleanRemotes(cf, connection):
пытается сначала удалить все файлы в каталоге на сервере, чтобы ликвидировать устаревшие копии if cf.cleanall:
for remotename in connection.nlst(): # список файлов на сервере
try: # удаление файла на сервере
print(‘deleting remote‘,remotename) # пропустить . и ..
connection.delete(remotename)
except:
print(‘cannot delete remote’, remotename)
def uploadAll(cf, connection)
выгружает все файлы в каталог на сервере в соответствии с настройками cf listdir() отбрасывает пути к каталогам, любые ошибки завершают сценарий localfiles = os.listdir(cf.localdir) # listdir — список локальных файлов for localname in localfiles:
localpath = os.path.join(cf.localdir, localname) print(‘uploading’, localpath, ‘to’, localname, ‘as’, end=’ ‘) if isTextKind(localname):
# использовать текстовый режим передачи localfile = open(localpath, ‘rb’) connection.storlines(‘STOR ‘ + localname, localfile) else:
# использовать двоичный режим передачи localfile = open(localpath, ‘rb’) connection.storbinary(‘STOR ‘ + localname, localfile) localfile.close()
connection.quit()
print(‘Done:’, len(localfiles), ‘files uploaded.’)
if __name__ == ‘__main__’:
cf = configTransfer(site=’learning-python.com’, rdir=’books’, user=’lutz’)
conn = connectFtp(cf) cleanRemotes(cf, conn) uploadAll(cf, conn)
Благодаря повторному использованию программного кода сценарий выгрузки не только стал заметно проще, но он также будет наследовать все изменения, которые будут выполняться в модуле загрузки. Например, функция isTextKind позднее была дополнена программным кодом, который добавляет расширение .pyw в таблицы mimetypes (по умолчанию тип этих файлов не распознается), — поскольку теперь это совместно используемая функция, изменения автоматически будут унаследованы и программой выгрузки.
Этот сценарий и программы, которые будут его импортировать, достигает тех же целей, что и оригинал, но простота обслуживания, которую они приобретают, имеет большое значение в мире разработки программного обеспечения. Ниже приводится пример загрузки сайта с одного сервера и выгрузки его на другой сервер:
C:\…\PP4E\Internet\Ftp\Mirror> python downloadflat_modular.py test
Clean target directory first?
Password for lutz on home.rmi.net:
connecting…
downloading 2004-longmont-classes.html to test\2004-longmont-classes.html as text
…часть строк опущена…
downloading relo-feb010-index.html to test\relo-feb010-index.html as text
Done: 297 files downloaded.
C:\…\PP4E\Internet\Ftp\Mirror> python uploadflat_modular.py test Clean target directory first?
Password for lutz on learning-python.com: connecting…
uploading test\2004-longmont-classes.html to 2004-longmont-classes.html as text
…часть строк опущена…
uploading test\zopeoutline.htm to zopeoutline.htm as text Done: 297 files uploaded.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011