Версия на основе классов

versiya na osnove klassov Сценарии на стороне клиента

Подход на основе функций, использованный в последних двух примерах, решает проблему избыточности, но эти реализации местами выглядят несколько неуклюжими. Например, объект 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=’anonymouspass=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/xpythonwin‘, ‘.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

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий