Подход на основе функций, использованный в последних двух примерах, решает проблему избыточности, но эти реализации местами выглядят несколько неуклюжими. Например, объект cf с параметрами настройки, образующий собственное пространство имен, замещает глобальные переменные и разрывает зависимости между файлами. При этом, начав создавать объекты, моделирующие пространства имен, можно заметить, что поддержка ООП в языке Python обеспечивает более естественный способ организации программного кода. В качестве последнего витка развития в примере 13.14 приводится еще одна попытка реорганизовать программный код для работы с FTP, использующий возможности классов в языке Python.
Пример 13.14. PP4E\Internet\Ftp\Mirror\ftptools.py
#!/bin/env python
############################################################################ использует протокол FTP для загрузки из удаленного каталога или выгрузки в удаленный каталог всех файлов сайта; для организации пространства имен и обеспечения более естественной структуры программного кода в этой версии используются классы и приемы ООП; мы могли бы также организовать сценарий как суперкласс, выполняющий загрузку, и подкласс, выполняющий выгрузку, который переопределяет методы очистки каталога и передачи файла, но это усложнило бы в других клиентах возможность выполнения обеих операций, загрузки и выгрузки; для сценария uploadall и, возможно, для других также предусмотрены методы, выполняющие выгрузку/загрузку единственного файла, которые используются в цикле в оригинальных методах;
############################################################################
import os, sys, ftplib
from getpass import getpass
from mimetypes import guess_type, add_type
# значения по умолчанию для всех клиентов dfltSite = ‘home.rmi.net‘
dfltRdir = ‘.’
dfltUser = ‘lutz’
class FtpTools:
# следующие три метода допускается переопределять
def getlocaldir(self):
return (len(sys.argv) > 1 and sys.argv[1]) or ‘.’
def getcleanall(self):
return input(‘Clean target dir first?’)[:1] in [‘y’,’Y’]
def getpassword(self):
return getpass(
‘Password for %s on %s:’ % (self.remoteuser, self.remotesite))
def configTransfer(self, site=dfltSite, rdir=dfltRdir, user=dfltUser):
принимает параметры операции выгрузки или загрузки из значений по умолчанию в модуле, из аргументов, из командной строки, из ввода пользователя
анонимный доступ к ftp: user=’anonymous‘ pass=emailaddr self.nonpassive = False # пассивный режим FTP по умолчанию в 2.1+ self.remotesite = site # удаленный сайт куда/откуда вып-ся передача self.remotedir = rdir # и каталог (‘.’ — корень учетной записи) self.remoteuser = user
self.localdir = self.getlocaldir()
self.cleanall = self.getcleanall()
self.remotepass = self.getpassword()
def isTextKind(self, 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(self):
print(‘connecting…’)
connection = ftplib.FTP(self.remotesite) # соединиться с FTP—сайтом
connection.login(self.remoteuser, self.remotepass) # зарегистрироваться connection.cwd(self.remotedir) # перейти в каталог
if self.nonpassive: # переход в активный режим FTP
# при необходимости
connection.set_pasv(False) # большинство — в пассивном режиме self.connection = connection
def cleanLocals(self):
пытается удалить все локальные файлы, чтобы убрать устаревшие копии if self.cleanall:
for localname in os.listdir(self.localdir): # локальные файлы
try: # удаление файла
print(‘deleting local’, localname)
os.remove(os.path.join(self.localdir, localname))
except:
print(‘cannot delete local’, localname)
def cleanRemotes(self):
пытается сначала удалить все файлы в каталоге на сервере, чтобы ликвидировать устаревшие копии if self.cleanall:
for remotename in self.connection.nlst(): # список файлов
try: # удаление файла
print(‘deleting remote’, remotename)
self.connection.delete(remotename)
except:
print(‘cannot delete remote’, remotename)
def downloadOne(self, remotename, localpath):
загружает один файл по FTP в текстовом или двоичном режиме имя локального файла не обязательно должно соответствовать имени удаленного файла if self.isTextKind(remotename):
localfile = open(localpath, ‘w’,
encoding=self.connection.encoding)
def callback(line): localfile.write(line + ‘\n’)
self.connection.retrlines(‘RETR ‘ + remotename, callback)
else:
localfile = open(localpath, ‘wb’)
self.connection.retrbinary(‘RETR ‘ + remotename, localfile.write) localfile.close()
def uploadOne(self, localname, localpath, remotename):
выгружает один файл по FTP в текстовом или двоичном режиме имя удаленного файла не обязательно должно соответствовать имени локального файла if self.isTextKind(localname):
localfile = open(localpath, ‘rb’)
self.connection.storlines(‘STOR ‘ + remotename, localfile) else:
localfile = open(localpath, ‘rb’)
self.connection.storbinary(‘STOR ‘ + remotename, localfile) localfile.close()
def downloadDir(self):
загружает все файлы из удаленного каталога в соответствии
с настройками; метод nlst() возвращает список файлов, dir() — полный список с дополнительными подробностями remotefiles = self.connection.nlst() # nlst — список файлов
# на сервере
for remotename in remotefiles:
if remotename in (‘.’, ‘..’): continue
localpath = os.path.join(self.localdir, remotename)
print(‘downloading’, remotename, ‘to’, localpath, ‘as’, end=’ ‘) self.downloadOne(remotename, localpath)
print(‘Done:’, len(remotefiles), ‘files downloaded.’)
def uploadDir(self):
выгружает все файлы в каталог на сервере в соответствии с настройками listdir() отбрасывает пути к каталогам, любые ошибки завершают сценарий localfiles = os.listdir(self.localdir) # listdir — локальные файлы for localname in localfiles:
localpath = os.path.join(self.localdir, localname)
print(‘uploading’, localpath, ‘to’, localname, ‘as’, end=’ ‘) self.uploadOne(localname, localpath, localname)
print(‘Done:’, len(localfiles), ‘files uploaded.’)
def run(self, cleanTarget=lambda:None, transferAct=lambda:None):
выполняет весь сеанс FTP
по умолчанию очистка каталога и передача не выполняются
не удаляет файлы, если соединение с сервером установить не удалось self.connectFtp() cleanTarget() transferAct()
self.connection.quit()
if __name__ == ‘__main__’:
ftp = FtpTools() xfermode = ‘download’ if len(sys.argv) > 1:
xfermode = sys.argv.pop(1) # получить и удалить второй аргумент if xfermode == ‘download’:
ftp.configTransfer()
ftp.run(cleanTarget=ftp.cleanLocals, transferAct=ftp.downloadDir) elif xfermode == ‘upload’:
ftp.configTransfer(site=’learning-python.com’, rdir=’books’, user=’lutz’)
ftp.run(cleanTarget=ftp.cleanRemotes, transferAct=ftp.uploadDir) else:
print(‘Usage: ftptools.py ["download" | "upload"] [localdir]’)
Фактически этот последний вариант объединяет в одном файле реализацию операций выгрузки и загрузки, потому что они достаточно тесно связаны между собой. Как и прежде, общий программный код был выделен в отдельные методы, чтобы избежать избыточности. Новым здесь является то обстоятельство, что для хранения параметров используется сам экземпляр класса (они превратились в атрибуты объекта self). Изучите программный код этого примера, чтобы лучше понять, как была выполнена реорганизация.
Эта версия действует, как и оригинальные сценарии загрузки и выгрузки сайта. Подробности, касающиеся использования, вы найдете в конце модуля, в программном коде самотестирования, а выполняемая операция определяется аргументом командной строки со значением «down- load» (загрузка) или «upload» (выгрузка). Мы не изменили принцип действия, а реорганизовали программный код, чтобы его проще было сопровождать и использовать в других программах:
C:\…\PP4E\Internet\Ftp\Mirror> ftptools.py download test
Clean target dir 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> ftptools.py upload test
Clean target dir 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.
Хотя этот файл по-прежнему можно запускать как сценарий командной строки, в действительности его класс теперь представляет собой пакет инструментов для работы с протоколом FTP, который можно подмешивать к другим классам и повторно использовать в других программах. Программный код, обернутый в класс, проще поддается адаптации под более специализированные требования путем переопределения его методов — его методы, возвращающие параметры настройки, такие как getlocaldir, например, можно переопределять в подклассах для решения специфических задач.
Но самое важное, пожалуй, заключается в том, что классы упрощают многократное использование программного кода. Клиенты этого файла смогут выгружать и загружать каталоги за счет создания подклассов или встраивания экземпляров этого класса и вызова их методов. Чтобы увидеть один из примеров такого применения, перейдем к следующему разделу.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011