14-й час

Лаборатория доктора Франкенштейна

  В предыдущей главе речь шла о специальных методах класса — одном из тех средств, которые делают Python особо дружественной средой объектно-ориентированного программирования (ООП). На этот раз мы собираемся применить ещё одну концепцию ООП — возможность повторного использования программных блоков. И хотя в текущем проекте мы не будем использовать классы, тем не менее, Вы в полной мере освоите все приёмы и методы разработки полноценных программных приложений. Не всё получится сразу, как нам хотелось бы, поэтому к нашей лаборатории "Франкенштейна" мы ещё вернёмся в следующей главе, чтобы воплотить в жизнь новые знания. Познакомившись с материалом этих двух глав, Вы научитесь создавать модули, специально предназначенные для повторного использования; документировать эти модули; добавлять в модули встроенные тесты; разбираться в кодах модулей, созданных другими программистами для общего пользования.

Создание программных компонентов для многократного использования

  Всякий раз, когда Вам приходится создавать функцию или метод для выполнения той или иной рутинной задачи, которую наверняка Вам ещё не раз придётся решать в этом и последующих проектах, задумайтесь над созданием модуля многократного использования. Если такой модуль будет построен в соответствии с общепринятыми соглашениями, то его эффективно сможете использовать в своих программах не только Вы, но и другие программисты.

  Пару лет тому назад я обнаружил, что некоторые из программ на языке Python, помещённые на моём Web-сервере, не работают. Я тщательно исследовал возникшую проблему и выяснил, что виной этому были неправильно записанные первые строки в файлах. Дело в том, что я работаю с сервером Apache Web в Windows NT. Считается, что такое решение позволяет настраивать Web-сервер и управлять им также, как в системах UNIX и Linux. В результате, хотя для всего мира мой сервер виден в среде NT, мне удалось зеркально отобразить его на две различные системы Linux (одна — на работе, другая — дома). Раздражающий недостаток состоит в том, что в версиях Apache для разных систем довольно много расхождений. Например, при копировании файлов страниц из системы в систему приходится вносить изменения в первых строках файлов для каждого интерфейса общего шлюза(CGI). Естественно, я часто забывал это делать, что приводило к серьёзным ошибкам. Пользователи не смогли пользоваться моим календарем Майя (http://www.pauahtun.org/tools.html), как я им это обещал, и слали мне гневные письма. Я решил воспользоваться языком Python для написания коротенькой программы, которая автоматически вносила бы нужные изменения в соответствующие файлы.

  *Прим. В. Шипкова: лично я предпочёл бы вынести все системные пути в виде строк в отдельный файл, и там, с помощью встроенных средств проверял ОС. В зависимости от результатов менял бы переменную. Для программы все эти махинации остались бы неизвестны. ;)

Новый термин

  Интерфейс CGI (Common Gateway Interface — интерфейс общего шлюза) — это способ, посредством которого приложения Web-серверов собирают информацию от пользователей. Для этой цели на Web-страницу обычно помещается специальная форма, а для обработки введённой в неё информации используется соответствующий сценарий CGI. Эти сценарии ещё иногда называют программами корзины CGI (cgi-bin program). Как правило, эти сценарии пишутся на языке Perl, иногда на С, но в последнее время все чаще для этой цели используется Python. В главе 24 мы рассмотрим несколько примеров сценариев CGI на языке Python.

  Поскольку с такими проблемами могут столкнуться читатели этой книги, я решил предоставить код моей программы на обозрение публике. Начал я с достаточно простой программы, предназначавшейся для достижения всего лишь одной цели. Мы наделим её некоторой функциональностью, хотя, безусловно, для Вас останется ещё множество непройденных дорог для её совершенствования и расширения возможностей. Но те этапы, которые я намерен пройти за время планирования и создания данной программы, окажутся полезными и поучительными для Вас. Перед тем как приступить к работе, я сформулировал для себя следующий список вопросов.

  Проблема возникла из-за того, что сервер Apache в системе Windows или UNIX требует, чтобы файлы сценариев CGI, которые не являются исполняемыми модулями (т.е. написаны не в машинном коде), должны начинаться строкой с двумя специальными символами (#!), за которыми следует абсолютный путь к программе, ответственной за выполнение этого сценария. Для установленной у меня системы Red Hat Linux это означает, что первая строка любой программы на Python должна быть #!/usr/bin/python, а та же строка в системе NT принимает вид #!с:\python\python.exe. Другие системы UNIX и Linux часто помешают Python в папку /usr/local/bin/, благодаря чему в этих системах первая строка будет выглядеть как #!/usr/local/bin/python. Беглый поиск по Internet показал, что никто ещё не писал программ для решения подобных проблем, однако мне удалось обнаружить ссылку на модуль, который содержал как раз ту глобальную переменную, которая была мне нужна: sys.executable. Эта переменная содержит строку пути к интерпретатору Python. Таким образом, первые несколько строк моей программы приняли следующий вид:

import sys

pbang = "#!" + sys.executable

if sys.platform == "Win32":

  sys.stderr.write ("Python Version "+\

  sys.version+"Running on Windows\n"

elif sys.platform == "linux2":

  sys.stderr.write ( "Python Version " + \

  sys.version + " Running on Linux\n" )

else:

  sys.stderr.write ( "Python Version " + \

  sys.version + " Running on " + sys.platform + "\n" )

  print pbang

  Комбинацию символов #! в английской терминологии часто называют hash-bang, что на русский можно перевести приблизительно как "удар топором". Существует ещё множество полуофициальных сленговых названий для обозначения разных символов и их комбинаций. Не всегда можно понять, что именно навеяло то или иное название. Так, символ * иногда называют shplat (знать бы, что это такое), для символа < придумали suck (просто чмок), а для его побратима > — spit (плевок), пару фигурных кавычек { и } называют squiggles (локоны) и т.п. Требуемая информация о размещении программы Python на Вашем компьютере должна содержаться в переменной pbang. Чтобы вывести её на экран, проще всего сохранить приведённый выше код в файле fixhashl.py и запустить на выполнение, как показано на рис. 14.1.

Рис. 14.1. Где живет Python?

  В своей программе для вывода значения переменной pbang вместо обычной функции print я использовал функцию sys.sterr.write(), чтобы быть уверенным в том, что пользователи всегда смогут прочитать выводимое сообщение. Инструкция print, использующая по умолчанию объект sys.stdout, легко может быть перенаправлена в любое другое место. Если пользователь воспользуется командой python fixhash.py > tempfile, то после выполнения программы на экране не будет каких-либо сообщений о местонахождении Python и о том, что произошло с программой. Такой интерфейс программы нельзя назвать дружественным.

Объекты stdin, stdout и stderr унаследованы от соответствующих потоков ввода-вывода системы UNIX. В Python они автоматически открываются при запуске любой программы и являются объектами файлов. Эти имена означают стандартный ввод, стандартный вывод и стандартное сообщение об ошибке. В UNIX все эти объекты доступны программам в любой момент, чего нельзя сказать о Windows. Программы с родословной, уходящей корнями в UNIX, а к таковым относится и Python, должны использовать специальные функции, чтобы гарантировать доступность объектов ввода-вывода.

  Поскольку я планировал использовать программу для изменения большого числа файлов, логично было в ядре программы заложить цикл, повторяющийся столько раз, сколько файлов нужно было обработать:

for i in список_файлов :

  if i is not эта_программа :

    проверить первую строку файла

    if строка is not pbang:

      изменение файла

  Обратите внимание, что приведённый вверху листинг вовсе не программа на языке Python, а то, что мы обычно называем псевдокодом. Псевдокод представляет собой стенографический способ описания расширенной структуры программы. При планировании структуры программы вообще не обязательно тратить время на соблюдение всех требований синтаксиса программирования, поскольку псевдокод необходим для организации ваших мыслей, а не для выполнения на компьютере. Поэтому нет необходимости беспокоиться о возможных ошибках, лишь бы Вам стало ясно, как должна работать программа. Только после того как в псевдокоде программы станет ясно, что должен делать каждый блок и как он работает, приступайте к написанию настоящей программы. Написание программы обычно является циклическим процессом, поскольку в процессе создания и тестирования блоков программы часто возникают новые требования к программе и реализацию следует вновь начинать с написания псевдокода.

  Первый шаг состоит в том, чтобы получить список файлов. Это довольно просто для тех, кто знает, что модуль os содержит функцию os.listdir(), специально предназначенную для этой цели. Следовательно, первые строки главной части программы должны быть такими:

dlist = os.listdir (".")

nfiles = 0

for i in dlist:

  if i != имя_программа

  Как определить имя программы? Я категорически не хочу жёстко вносить в код имя файла, хотя это и было моим первым побуждением. Пришлось сделать усилие над собой, чтобы отказаться от первого подвернувшегося простейшего решения. Но тут я вспомнил, что модуль sys обеспечивает доступ к списку argv, содержащему все параметры, передаваемые при запуске программы. Первым элементом этого списка всегда является имя программы. Поскольку имя программы всегда содержит полный путь к выполняемому файлу, следует отбросить всю часть строки, предшествующую собственно имени файла. Это можно легко сделать следующим образом:

nm = os.path.split(sys.argv[0])[1]

  Функция os.path.split() возвращает список, содержащий два элемента: путь и имя файла. Поэтому, чтобы присвоить переменной nm только имя файла, воспользуемся оператором индексирования и возвратим второй элемент списка ([1]). Строка пути останется в первом элементе списка (с индексом 0). На этом этапе наш программный код имеет следующий вид:

nm = os.path.split (sys.argv[0])[1]

dlist = os.listdir ('.')

nfiles = 0 for i in dlist:

if i == nm:

  continue

  ...проверка первой строки файла...

  Один из способов проверки первой строки файла состоит в том, чтобы открыть этот файл, применив для этой цели функцию ореn(), и считать первую строку, после чего сравнить эту строку с той, которая представлена в переменной pbang. Однако следует учесть, что в будущем нам понадобится не только возможность проверки пути, но и возможность изменения его в файле. При работе с другими языками программирования, такими как С, чтобы внести изменения в первую строку файла, нужно перегибать не только её, но и весь файл. Такой подход требует создания временной копии файла, что само по себе неприятно. Представьте, что для изменения единственной строки Вам потребуется создание копии огромного файла. Это ли не пример крайней неэффективности работы программы. Я порылся в документации модуля fileinput, представленной по адресу www.python.org, и нашел то, что искал. Этот модуль содержит метод, позволяющий построчно считывать файл и тут же вносить изменения в текущую строку без создания временного файла. Вот он — ключ к достижению максимальной эффективности! Осталось написать функцию, которая будет переписывает первую строку файла, если в этом возникнет необходимость. Ядро программного кода приняло следующий вид:

nm = os.path.split (sys.argv[0])[1]

dlist = os.listdir (".")

nfiles = 0

for i in dlist:

  if i == nm:

    continue

  if len(i) > 4 :

    if i[-3:] == ".py":

      sys.stderr.write("Checking " + i + "...\n")

      for line in fileinput.input(i):

        if fileinput.isfirstline():

          if line[:-l] != pbang:

            fileinput.close()

            t = os.access(i,os.W_OK)

            if t == 0:

              sys.stderr.write(i + " is not writable;\

              skipping.\n")

            else:

              sys.stderr.write("Modifying " + i +\

              "...\n" )

              rewritefile(i)

              nfiles = nfiles + 1

              break

            else:

              fileinput.close()

              break

sys.stderr.write("Files modified: " + 'nfiles' + "\n")

  Комментарий: модуль os содержит метод access(), который позволяет проверить, возможно ли открыть указанный файл для записи. Если файл разрешено открывать только для чтения, программа пропустит его, показав сообщение для пользователя. Имя os.W_OK представляет собой специальный флаг, сообщающий методу access(), что нам необходимо возвратить установку допуска для записи к этому файлу. Метод isfirstline() проверяет, работаем ли мы с первой строкой файла. Возвращённая первая строка сравнивается со значением переменной pbang. Если первая строка и значение pbang совпадают, следует команда: закрыть этот файл и перейти к следующему. Для этого используется инструкция break в конце программы, которая прерывает вложенный цикл for. Если же значения не совпадают, вызывается функция rewritefile() с именем текущего файла в качестве аргумента. Обычно перед сравнением имён файлов их приводят к нижнему регистру, но поскольку системы UNIX чувствительны к регистру, я решил, что лучше будет лишний раз переписать первые строки файлов в Windows, чем пропустить хотя бы одну несовпадающую запись в UNIX. Последняя строка сообщает пользователю, сколько файлов было изменено в ходе выполнения программы.

  Чтобы проверить правильность кода, можно временно заменить функцию rewritefile() на так называемую функцию-заглушку (stub-function). Это функция, имеющая ту же сигнатуру (набор параметров), что и заменяемая функция, но в то же время не выполняющая никаких действий или просто выводящая сообщение для отладки программы. Например, можно создать функцию, которая будет выводить имена файлов текущего каталога. Вставьте эту функцию в файл fixhash.py и запустите программу на выполнение, чтобы проверить правильность её работы.

Ниже показан настоящий код функции rewritefilef).

def rewritefile(fip):

  mode = os.stat(fip)[ST_MODE]

  for line in fileinput.input(fip, inplace=1):

    if fileinput.isfirstline():

      if line [:2] == "*!" :

        sys.stdout.write(pbang + "\n")

      else:

        sys.stdout.write(pbang+"\n")

        sys.stdout.write(line)

    else:

      sys.stdout.write(line)

      fileinput.close()

      os.chmod(fip, mode)

  Комментарий: поскольку мы имеем дело с функцией, в ней есть ссылки на переменные, определённые в модуле, но функция не может присваивать, им какие-либо значения. Так, мы не можем изменить значение переменной pbang, если только явно не откроем к ней доступ с помощью инструкции global pbang. Но значение этой переменной должно устанавливаться только однажды по контексту открытия модуля, после чего оно остаётся константным. Поэтому невозможность изменения переменной pbang функциями модуля вполне нас удовлетворяет. Модуль os содержит функцию stat(), которая возвращает всю информации о состоянии файлов и каталогов. Нас в данном случае интересует состояние открываемых файлов. Но чтобы получить определение ST_MODE, следует импортировать модуль stat. В данном случае вполне подойдёт выражение from stat import *. В действительности нас совершенно не интересуют текущие состояния файлов, просто после их изменения следует восстановить исходные установки. По флагам состояния файла можно определить, является ли он исполняемым и можно ли его открывать для записи или только для чтения. В ходе изменения содержимого файла часто автоматически изменяются установки флагов состояния, поэтому перед закрытием файла необходимо восстановить его исходное состояние. В действительности это важно только для системы UNIX и совершенно не важно для Windows. Но в целях поддержания переносимости программы всегда следует запоминать и восстанавливать флаги состояния изменяемых файлов.

  Выражение for line in fileinput.input(fip, inplace=l): основано на одном исключительно полезном свойстве модуля fileinput. Ключевой аргумент inplace=l задаёт особый режим открытия файла, позволяющий тут же вводить изменения в текущие строки. При вызове функции fileinput.input() с данным аргументом автоматически создаётся копия файла, после чего он открывается повторно в режиме для записи. С этого момента любые вызовы функции write() или инструкции print, которые связаны с объектом stdout, передают данные в новый файл. После открытия файла программа проверяет его первую строку. Если строка начинается с символов #!, то она замешается на стандартную (содержащуюся в переменной pbang). Если же файл начинается как-то иначе, то стандартная строка добавляется в начало файла, после чего все содержимое файла, строка за строкой, передаётся в объект stdout, который тут же перенаправляет их в новый файл. Когда происходит вызов функции fileoutput.close(), новый файл закрывается, а копия исходного файла удаляется из оперативной памяти компьютера.

  В результате мы получили новую версию программы fixhash.py, код которой показан в листинге 14.1.

Листинг 14.1. Завершенная программа fixhash.py

#!c:\Spez\Python-2-4\python.exe

import os
import sys
import fileinput
from stat import *

pbang = "#!" + sys.executable
if sys.platform == "win32" :
  sys.stderr.write ( "Python Version "+sys.version+"\

    Running on Windows\n" )
elif sys.platform == "linux2":
  sys.stderr.write("Python Version "+sys.version+"\

    Running on Linux\n" )
else:
   sys.stderr.write ( "Python Version "+sys.version+"\

     Running on " + sys.platform + "\n" )

nm = os.path.split ( sys.argv[ 0 ] )[ 1 ]

def rewritefile ( fip ) :
 
global pbang
  mode = os.stat ( fip )[ ST_MODE ]
 
for line in fileinput.input ( fip, inplace = 1 ) :
   
if fileinput.isfirstline():
     
if line [ : 2 ] == "#!" :
        sys.stdout.write ( pbang + "\n" )
     
else:
        sys.stdout.write ( pbang + "\n" )
        sys.stdout.write ( line )
   
else:
      sys.stdout.write ( line )
      fileinput.close ( )
      os.chmod ( fip, mode )

dlist = os.listdir ( "." )
nfiles = 0
for i in dlist :
 
if i == nm :
   
continue
 
if len ( i ) > 4 :
   
if i[ -3 : ] == ".py" :
      sys.stderr.write ( "Checking " + i + "...\n" )
     
for line in fileinput.input ( i ) :
       
if fileinput.isfirstline ( ) :
         
if line[ : -1 ] != pbang :
            fileinput.close ( )
            t = os.access ( i, os.W_OK )
           
if t == 0 :
             sys.stderr.write ( i + " is not writable;\

               skipping.\n" )
          
else :
             sys.stderr.write("Modifying "+i+"...\n" )
             rewritefile ( i )
             nfiles = nfiles + 1
           
 break
          
else :
             fileinput.close ( )
            
break

sys.stderr.write("Files modified: "+`nfiles`+"\n" )

  Представленная выше программа fixhash.py — это законченное работоспособное приложение, превосходно справляющееся с поставленной задачей. Никакие другие изменения не потребуются до тех пор, пока с ней будете работать только Вы сами. Если же Вы хотите превратить её в модуль, который сможете использовать Вы и другие программисты в качестве компонента своих программ, следует ещё немножко поработать.

  Самой первой полезной модернизацией будет добавление аргумента командной строки, задающего текущий каталог. Это позволит Вам сохранить файл fixhash.py в общем каталоге, где хранятся все файлы сценариев, вместо того чтобы копировать этот файл каждый раз в тот каталог, который нужно проанализировать. В системе UNIX таким общим местом хранения файлов сценариев обычно является каталог bin, вложенный в другой общедоступный каталог, например /usr/local/bin. В Windows, однако, стандартных папок для этих целей не существует. Я на своём компьютере использую инструментальный набор средств MKS для Windows, позволяющий воспроизвести среду UNIX. В этом случае для сценариев общего доступа рекомендуется использовать каталог mksnt (для любой версии Windows это обычно c:\mksnt). Вы можете выбрать любую папку на Вашем компьютере, наиболее удобную для Вас.

  Если проанализировать главную часть программы, то совсем не трудно определить, какие именно изменения необходимо внести, чтобы стала возможной поддержка аргумента командной строки. В листинге 14.2 показаны необходимые изменения, которые свелись к определению новой функции fixdirectory и добавлению блока

if __name__ == "__main__" :

....

Листинг 14.2. Добавление поддержки аргумента командной строки в программу fixhash.py

def fixdirectory(d):

  dlist = os.listdir ( d )

  nfiles = 0

  for i in dlist :

    if i == nm :

      continue

    if len ( i ) > 4 :

      if i[ -3 : ] == ".py" :

        sys.stderr.write ( "Checking " + i + "__\n" )

    for line in fileinput.input ( i ) :

      if fileinput.isfirstline ( ) :

        if line[ :-1]!= pbang :

          fileinput.close ( )

        t = os.access ( i, os.W_OK )

        if t == 0 :

          sys.stderr.write(i+\

             " is not writable;skipping. \n")

        else :

          sys.stderr.write ( "Modifying " + i + "...\n"

          rewritefile ( i )

          nfiles = nfiles + 1

          break

      else :

        fileinput.close ( )

        break

  return nfiles

 

if __name__ == "__main__" :

  nmod = 0

  if len ( sys.argv ) > 1 :

    dirnames = sys.argv[ 1 : ]

  else :

    dirnames = [ "." ]

    for s in dirnames :

      nmod = nmod + fixdirectory ( s )

 sys.stderr.write ( "Files modified: " + 'nmod' + "\n" )

  После внесения указанных изменений я запустил программу fixhash.py на выполнение сначала из текущего каталога, при этом никаких проблем не возникало, а после этого из общего каталога сценариев с указанием проверяемого каталога C:\DB в аргументе командной строки. На рис. 14.2 показано, что произошло во втором случае.

Рис. 14.2. Приехали?!!

  А-а-а, я забыл одну важную вещь (честное слово, со мной это случается не часто). Аргумент командной строки работает нормально, однако, несмотря на то, что программа находит нужный каталог (в противном случае откуда она знает, какой файл предполагается открыть), нужный файл она не может открыть. Это объяснятся тем, что модулю fileinput передаётся только имя файла, но не путь к нему. Это нелегко исправить, как показано в листинге 14.3.

Листинг 14.3. Внесение исправлений в файл fixhash.py

def fixdirectory ( d ) :

  dlist = os.listdir ( d )

  nfiles = 0

  for i in dlist:

    if i == nm:

      continue

    if len ( i ) > 4 :

      fname = d + "7" + i

    if fname[ -3 : ] == ".py" :

      sys.stderr.write ( "Checking " + fname + "...\n" )

        for line in fileinput.input ( fname ):

          if fileinput.isfirstline ( ):

            if line[ :-1]!= pbang:

              fileinput.close ( )

              t = os.access { fname, os.W_OK )

            if t == 0:

              sys.stderr.write ( fname + " is not\

                 writable; skipping.\n" )

            else:

              sys.stderr.write ( "Modifying " + fname +\

                ...\n" )

              rewritefile ( fname )

              nfiles = nfiles + 1

              break

            else:

              fileinput.close ( )

              break

            return nfiles

Если теперь мы запустим программу снова, то получим правильный результат, как показано на рис. 14.3.

Рис. 14.3. Теперь с помощью программы fixhash.py можно изменять файлы в любом каталоге

  Обратите внимание, что даже в Windows нет необходимости использовать в строке пути обратные косые. Часто приходится ломать голову, какую косую черту использовать. Но Python автоматически преобразует обычную косую черту в обратную, если ваша операционная система потребует это.

  А что дальше? Мы успешно добавили аргумент командной строки. Между прочим, цикл for s in dirnames: (строка 32 в листинге 14.2) обеспечивает возможность того, что несколько имён каталогов, указанных в командной строке, будут обрабатываться правильно. Но можно ли ещё как-то расширить функциональные возможности программы fixhash.py? Было бы здорово, если бы функция fixdirectory() искала файлы Python не только в указанном каталоге, но и во всех вложенных каталогах. Такой поиск файлов называется рекурсивным.

  Это совсем не трудно реализовать. Я внимательно просмотрел информацию обо всех доступных модулях в документации на Python, и моё внимание привлекла функция path.walk() из модуля os. Она оказалась именно тем, что нам нужно. Поскольку мы уже пользуемся функцией path.split() из модуля os, нам даже не нужно импортировать никакие новые модули. Кроме того, в этом модуле есть ещё одна полезная функция path.join(), с помощью которой удобно выполнять конкатенацию строк там, где это нужно было в листинге 14.3. Функция path.join(} настолько сообразительна, что может даже самостоятельно в.строке пути расставлять недостающие косые. Так, запись fname=d+"/"+i будет равносильна выражению: fname= os.path.join(d,i)

  Если имя каталога d в рассматриваемом примере оканчивается косой чертой, функция не станет добавлять ещё одну косую, в то время как обычный оператор конкатенации строк именно так и поступит.

  В данный момент функция fixdirectory() принимает только один аргумент, но этого будет недостаточно, если мы хотим добавить функцию os.path.walk(). Ниже показан синтаксис этой функции: walk(path,visit, arg)

  А вот что говорится об этой функции в документации Python: "Вызывает функции visit с аргументами (arg, dirnarae, names) для каждого каталога в дереве каталогов, заданном аргументом path (путь), включая базовый каталог, заданный путём. Параметр dirname (имя каталога) определяет текущий подкаталог; параметр names (имена) представляет собой список файлов в этом каталоге, полученный в результате выполнения функции os.listdir(dirname)".

  Чтобы воспользоваться функцией walk(), нужно только вызвать её в главной части программы, передав имя текущего каталога (аргумент path), скорректированную нами версию функции fixdirectory(} (аргумент visit) и ещё один аргумент arg. Последний аргумент просто задаёт способ передачи данных функции visit. Обычно в этом аргументе нет необходимости, но при некоторых обстоятельствах он может оказаться весьма удобным средством модернизации функции от дерева к дереву. В листинге 14.4 представлен измененный код функции fixdirectory().

Листинг 14.4. Измененная версия функции fixdirectory()

def fixdirectory ( arg, d, dlist ):

  global nfiles

  nfiles = 0

  for i in dlist:

    if i == nm:

      continue

    if len ( i ) > 4:

      fname = os.path.join ( d, i )

      if fname[-3:] == ".py":

        sys.stderr.write ( "Checking "\

        + fname + "...\n" )

        for line in fileinput.input(fname):

          if fileinput.isfirstline():

            if line[:-1]!= pbang:

              fileinput.close()

              t = os.access(fname, os.W_OK)

            if t==0:

              sys.stderr.write(fname + " is not\

              writable;skipping.\n" )

            else:

              sys.stderr.write("Modifying "+fname +\

               ...\n")

              rewritefile(fname)

              nfiles=nfiles+1

              break

           else:

             fileinput.close()

             break

  Обратите внимание, как мало требуется внести изменений. Просто добавьте новые параметры, удалите обращение к функции os.listdir() и добавьте строку global nfiles. Последнее необходимо для того, чтобы определить число измененных файлов. Для определения этого числа в наш модуль нужно включить дополнительную функцию:

def getnfiles():

  return nfiles

  Разумеется, можно было бы рекомендовать пользователям определять число файлов посредством прямого доступа к переменной fixhash. nfiles, но, пожалуй, интерфейс будет менее сложным, если мы предусмотрим для этой цели простую функцию.

  Ещё нужно внести небольшие изменения в основную часть программы, как показано в листинге 14.5.

Листинг 14.5. Изменение программы fixhash.py с целью поддержания использоания функции оs.path.walk()

if __name__ == "__main__":

  nmod = 0

  if len( sys.argv )> 1:

    dirnames = sys.argv[1:]

  else:

    dirnames=["."]

    for s in dirnames:

      os.path.walk(s,fixdirectory,0)

      nmod=nmod+getnfiles()

   sys.stderr.write("Files modified:\

  " + 'nmod' + "\n" )

 

  На рис. 14.4 показан результат выполнения нашей модифицированной программы. В качестве аргумента был передан каталог, содержащий вложенный подкаталог, причём в обоих хранились файлы Python.

Рис. 14.4. Просмотр дерева каталогов программой fixhash.py

Резюме

  В этой главе мы рассмотрели все этапы разработки модулей для многократного использования. Был создан модуль fixhash, который мы затем многократно совершенствовали, добавляя всё новые и новые возможности. В следующей главе мы завершим создание этого модуля и выполним его документирование.

Практикум

Вопросы и ответы

Почему Python устанавливается в разных каталогах в различных версиях UNIX?

  Основная причина заключается в различии двух главных ветвей системы UNIX: BSD и System V. Изначально не было предусмотрено специальных мест для хранения пользовательских сценариев и программ. Но их с каждым годом накапливалось всё больше и больше. В силу этого обстоятельства компания Berkeley (BSD) выделила специальный каталог /usr/local. Позднее подобные каталоги были зарезервированы и в System V, причём были разработаны чёткие правила, регламентирующие размещение программ в этих каталогах. Различные версии системы Linux добровольно приняли на себя обязательство использовать некоторые или даже все эти каталоги как системы BSD, так и системы System V.

Контрольные вопросы

  1. Какой из предлагаемых ниже методов возвращения списка файлов в каталоге является наилучшим?

    а) os.path.list()

    б) os.list.dir()

    в) sys.listdir().

    г) sys.dir()

  2. Для чего используется переменная sys.platform?

    а) Сообщает об операционной системе, с которой Вы работаете.

    б) Сообщает о платформе Вашего компьютера.

    в) Сообщает о том, что Вы работаете с Active Desktop.

    г) Такой переменой нет.

Ответы

  1. б. Наилучший и, пожалуй, единственный из представленных способов (если Вы, конечно, не напишете свой метод) — воспользоваться методом os.list.dir(). Функция dir() решает совсем другую задачу.

  2. а. Переменная sys.platform сообщает Вам о том, на какой операционной системе выполняется ваша программа. Это позволит Вам избежать использования функций, которые недоступны в данной операционной системе.

Примеры и задания

  Великолепный пример формы, принимающей данные от пользователей, можно посмотреть по адресу http://www.ithaca.edu/library/Training/wwwquiz.html. На этой же странице Вы найдёте вполне приличный словарь терминов Internet.

  Попробуйте самостоятельно создать функцию-заглушку для подмены в нашей программе функции rewritefile(). Постарайтесь сделать так, чтобы программа не только работала, но и добавленная Вами функция могла сообщать полезную информацию об ошибках во время отладки программы.

  В строке 32 листинга 14.1 устанавливается поиск в текущем каталоге всех файлов, оканчивающихся стандартным для Python расширением .ру. Подумайте, что произойдет, если кто-нибудь присвоит вложенному каталогу имя типа spam.py. Найдите в документации по Python описание способа определить, является ли некоторый файл действительно файлом, а не каталогом. Добавьте соответствующий программный код в нашу программу fixhash.py. Подсказка: прочтите документацию к функции os.stat().

  Разработайте вариант функции fixdirectory(), нечувствительной к регистру букв. Попробуйте сделать так, чтобы ваша функция "теряла чувствительность" к регистру в системе Windows, но сохраняла её в других системах. Подсказка: просмотрите документацию к модулю string.