8-й час

Полезные средства и приёмы программирования

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

  Эти четыре средства чрезвычайно важны для написания полноценных программ на языке Python и лежат в его концептуальной основе.

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

В этой главе мы также обсудим следующие темы:

Три важные функции и одна инструкция языка Python

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

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

Функция mар()

  Функция mар является, пожалуй, самой лёгкой в применении и простой для понимания. Она вызывает функцию, название которой указывается в аргументе, для всех элементов последовательности, также представленной в строке аргументов. Вот синтаксис функции mар():

mар(функция, последовательность)

  Предположим, у Вас имеется строка символов, полученная из другой программы или записанная в формате, который не поддерживается программой. Например, Вам может потребоваться распаковать строку символов, содержащую значения даты. Первым шагом в решении этой задачи может быть разбиение строки на составляющие элементы, отделенные пробелами. Рассмотрим стандартную строку вывода даты, например ту, которая отображается на экране дисплея UNIX командой date. Эта строка выглядит приблизительно так: Thu Aug 5 05:51:55 MDT 2001

  Тогда для разбиения данной строки на составляющие значения можно применить следующий приём:

s=string.split("Thu Aug 5 05:51:55 MDT 2001")

  В этом случае переменная s будет представлять собой список, содержащий элементы ['Thu', 'Aug', '5', '05:51:55', 'MDT', '2001']

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

Листинг 8.1. Функция look()

def look(s):

  if s[0] in string.digits:

    return string.atoi(s)

  else:

    return s

  В данном случае функция look() выполняет довольно бесхитростный анализ. Тем не менее рассмотрим её подробнее. Данная функция анализирует первый символ переданного аргумента s и, если этот символ является цифрой, преобразовывает его в целочисленное значение. В противном случае она возвращает аргумент в том же самом виде, в каком он был получен. Это не всегда удобно, в чем можно убедиться при выполнении этой функции (листинг 8.2).

Листинг 8.2. Выполнение функции look()

import string

def look(s):

  if s[0] in string.digits:

    return string.atoi(s)

  else:

    return s

 

x = "Thu Aug 5 06:06:27 MDT 2001"

t = string.split(x)

lst = map(look,t)

print lst

  При выполнении представленной программы (хоть в окне интерпретатора, хоть в отдельном выполняемом файле) на экране появится негодующее сообщение:

ValueError: invalid literal for atoi(): 06:06:27

(Ошибка значения: 06:06:27 — недопустимый литерал для atoi()).

  Другими словами, Python жалуется, что ему велели преобразовать строку "06:06:27" в целое число, а он не может выполнить этого задания из-за присутствия в ней двоеточий. В листинге 8.3 приведена немного улучшенная версия функции look().

Листинг 8.3. Исправленный вариант функции look()

def look(s):

  if ':' in s:

    ts = string.split(s,':')

    return map(string.atoi,ts)

  if s[0] in string.digits:

    return string.atoi(s)

  else:

    return s

  Внесите эти исправления в код листинга 8.2 и вновь выполните программу. На этот раз на экране должна появиться следующая строка: ['Thu','Aug',5,[6,6,27],'MDT',2001]

  Обратите внимание, что значение времени ("06:06:27") было преобразовано в список [6, 6, 27], вложенный в список, возвращённый функцией mар(). Происходит следующая последовательность действий. Функция mар() вызывает функцию look() для каждого элемента списка t, который мы получили с помощью функции string.split(). В свою очередь, функция look() с помощью функции string.atoi() преобразовывает все цифровые элементы списка в целые числа.

  Функция mар() успешно применяется тогда, когда заранее известна исходная структура данных, как, например, в строковых значениях времени или даты, где отдельные значения разделены символами двоеточия или пробелами. Для дат в календаре Майя вполне подошел бы следующий формат записи: "12.19.6.7.12". Преобразование подобных строк в списки целых чисел — идеальное место приложения функции mар:

s ="12.19.6.7.12"

ls = string.split(s,'.')

md = map(string.atoi, ls)

  После выполнения этих строк кода переменная md будет содержать список [12,19,6,7,12].

  Далее на страницах этой книги я покажу несколько других полезных функций обработки строковых значений времени и даты. А пока перейдем к функции reduce().

Функция reduce()

  Эта функция предназначена для приведения указанной последовательности значений к одному значению с помощью функции, принимающей два аргумента и возвращающей вещественный результат. Вот её синтаксис: reduce(функция, последовательность)

  Например, с помощью функции reduce() можно эффективно превращать списки строковых значений в одну строку. Простой пример тому представлен в листинге 8.4.

Листинг 8.4. Использование функции reduce()

х = ['Thu', 'Aug', '5', '06:06:27', 'MDT', '2001']

def strp(x,y):

  return x+' '+у

r = reduce(strp,x)

print r

  Выполнение этой программы должно привести к выводу на экран воссоединенной строки даты. В листинге 8.5 показан пример с перемножением последовательности чисел.

Листинг 8.5. Числа и reduce()

n=range(1,11)

def mult(x,y):

  return x*y

f=reduce(mult,n)

print f

  На этот раз будет выведен результат вычисления факториала от числа 10 (в математике это выражение имеет вид 10!). В функцию reduce передаются список [1,2,3,4,5,6,7,8,9,10], возвращенный функцией range(), и имя функции mult(). Функция mult() принимает в качестве аргументов первые два элемента списка и возвращает результат из умножения. Затем она снова получает два аргумента, одним из которых является её собственным результатом, а вторым — очередной элемент списка. И так повторяется до конца списка. Вызов функции range(1,11) возвращает последовательный список целых чисел, начинающийся с 1 и заканчивающийся 10. Как Вы помните, функция range() всегда возвращает список, включающий границу слева и исключающий границу справа.

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

Функция filter()

  Как и в предыдущих двух случаях, список аргументов функции filter состоит из управляющей функции и последовательности значений: filter(функция,последовательность)

  В данном случае управляющая функция должна возвращать значение 1 или 0 (числа, традиционно применяемые для представления логических значений true (истинно) и false (ложно)), в зависимости от того, хотите Вы или нет, чтобы некоторый элемент исходной последовательности был включён в новую последовательность, возвращаемую функцией filter(). В примере, показанном в листинге 8.6, в качестве управляющей используется функция gregorian_leap(), с которой мы уже познакомились в предыдущей главе.

Листинг 8.6. Использование функции filter ()

def gregorian_leap(y):

  if (y%400) == 0:

    return 1

  elif (y%100) == 0:

    return 0

  elif (y%4) == 0:

    return 1

  return 0

 

n = range(1900,2001)

ly = filter(gregorian_leap, n)

print ly

  При выполнении этой программы формируется список, элементы которого представляют собой перечень всех високосных годов с 1900 по 2000 включительно.

Инструкция lambda

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

def mult(x,y):

  return x*у

  Затем мы передали имя этой функции в списке аргументов функции reduce. А вот как можно было выполнить ту же самую работу, но с меньшим числом нажатий клавиш:

f = reduce(lambda x,y: x*y,n)

  print f

  Аналогично можно было бы поступить и с другими двумя функциями — mар() и filter(). Синтаксис инструкции lambda следующий:

lambda список_параметров:

  выражение

  Обратите внимание, что параметры не заключаются в круглые скобки, что характерно для определения обычных функций. В списке параметры отделены друг от друга запятыми. Необходимо, чтобы после двоеточия в этой же строке следовало выражение функции. Любой символ, не используемый в выражениях, будет означать завершение выражения анонимной функции. Выражения представляют собой любую осмысленную совокупность операндов и операторов: х+у, х*у, s[9] и т.д. Кроме того, анонимную функцию можно снабдить заданным по умолчанию параметром, причём практически так же, как это делается для обычных функций:

lambda х, у=13: х*у

  Если в анонимную функцию, заданную инструкцией lambda, не будет передан второй аргумент, то по умолчанию будет использовано значение 13.

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

  *Прим. В. Шипкова: функция lambda() довольно экзотична. И выше указаны её недостатки. НО! В тесте перемножения двух чисел, именно из-за усечённого пространства имён она быстрее выделенной функции на 30%. А это существенно! Впрочем, без всяких функций перемножение двух чисел с присвоением длилось на 47% быстрее чем с применением функции, и на 24% быстрее чем при применении функции lambda(). Также следует учесть: эта функция в качестве тела цикла принимает только ОДНО выражение.

  В примере, показанном в листинге 8.7, анонимная функция, заданная инструкцией lambda, будет работать не так, как можно было предположить.

Листинг 8.7. Ошибочное использование анонимной функции

def myfunc(x):

  increment = х + 1

  return lambda increment : increment + 1

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

  И наконец, Вы не можете использовать другие инструкции в выражениях анонимной функции, заданной инструкцией lambda. Это означает, что из анонимной функции нельзя выполнять печать с помощью инструкции print. Это ограничение также можно обойти, о чём Вы узнаете ниже, в разделе "Объекты файлов".

Вывод на экран и форматирование

  Вы уже много раз встречались с инструкциями print, но мы их использовали, практически ничего не зная о том, как они работают. Вам уже известно, что если в текст программы поместить инструкцию print, то она просто отобразит в окне терминала DOS все переменные и константы, указанные за ней:

х = ['Thu', 'Aug', '5', '06:06:27', 'MDT', '2001']

print x

  В результате выполнения представленного выше кода на экране появится следующая информация:

['Thu','Aug','5','06:06:27','MDТ','2001']

Как видите, что имели, то и получили. Примерно то же говорил первый президент Украины.

  *Прим. В. Шипкова: вообще-то это старый армейский оборот, который на публику вынес Черномырдин (ему же нагло и приписывается авторство журналистами.) :-)

  Оказывается, инструкции print проще всего работать со строковыми переменными, так как это именно тот тип данных, который она выводит. Тогда интерпретатору больше ничего не остаётся делать, кроме как непосредственно передать на дисплей уже готовую строку символов. Числа автоматически преобразовываются в строки символов, так же, как и списки, наборы и словари. Имена функций, однако, выводятся немного иначе. Если есть функция под именем myfunc и Вы пытаетесь вывести (обратите внимание, вывести, а не вызвать) её на экран с помощью print, на дисплее отобразится сообщение, подобное следующему: <function myfunc at 7cf8bO>

  Скорее всего, это не то, что Вы предполагали увидеть. Если функция возвращает значение, то можно вывести это значение таким выражением:

print myfunc()

  В этом случае происходит вызов функции myfunc(), а затем на экран выводится возвращенное значение.

  Как бы то ни было, потребность преобразования в строку символов чисел, последовательностей и словарей будет возникать довольно часто. Несомненно, существует немало способов выполнить эту задачу. Но самый быстрый и простой состоит в том, чтобы заключить всё, что необходимо преобразовать в строку, между символами обратного ударения (') (Данный символ, который в английской терминологии ещё называют backticks, вводится той же клавишей, что и символ тильда (~). Эта клавиша для большинства раскладок клавиатуры находится сразу под клавишей <Esc>. He спутайте символ обратного ударения с одинарной кавычкой, это принципиально важно.) Вот пример быстрого преобразования числа в строку:

х = 23

у = `х`

  Переменная х содержит число 23, тогда как переменная у теперь содержит строку символов "23". Это действительно обычная строковая переменная, которую можно обрабатывать как строку со всеми доступными операторами, например с помощью оператора индексирования. Эту же операцию можно реализовать с помощью встроенной функции str():

х = 23

у = str(x)

  Можно также воспользоваться встроенной функцией str(). Отличие между str() и repr() едва различимо: str() возвращает строковое представление переданных ей данных, тогда как repr() возвращает строковый объект, который затем можно преобразовать в исходное значение. Для чисел, последовательностей и словарей между этими функциями в принципе нет никакой разницы. Когда мы будем изучать определяемые пользователем объекты, то обсудим более обстоятельно, что ещё полагается выполнять функции repr(), хотя и в этом случае отличие невелико.

  Иногда возникает необходимость взять несколько переменных различных типов и объединить их в одну строку, возможно, даже вместе с союзами или знаками пунктуации, расставленными между ними. Конечно, это можно сделать с помощью оператора конкатенации строк, т.е. символа (+), как показано в листингу 8.8.

Листинг 8.8. Конкатенация строк

х = 20

у = 13

z = "х is "+'х.'+", у is "+'у'+" and х * у is "+'х * у'

print z

  Существует более совершенный метод решения подобных задач, который называется форматированием. Он не оказывает никакого влияния на оператор print, а состоит в использовании специального оператора форматирования (I). Код, показанный в листинге 8.9, является примером вывода той же самой строки, которую мы только что получили, но на этот раз с помощью оператора форматирования.

Листинг 8.9. Форматирование с помощью оператора %

х = 20

у = 13

print "х is id, у is %d and х*у is %d" % (х, у, х*у)

Символ * выполняет функцию оператора форматирования, если слева от него находится строка, а справа — набор. Операнды, находящиеся справа, необходимо записывать в виде набора (т.е. заключать в скобки) только в том случае, если количество элементов превышает единицу. Но значительно спокойнее и безопаснее завести привычку всегда заключать правый операнд в скобки. Присутствие скобок никогда не будет ошибкой, а отсутствие — может быть. Внутри строки левого операнда также используются символы %, причём их должно быть столько же, сколько элементов находится в наборе правого операнда. Символы % в паре с каким-то другим буквенным символом называются спецификаторами форматирования. Например, в предыдущем примере Вы могли заметить, что в строке трижды используются комбинации символов 'Id'. Это спецификаторы формата целых чисел. Каждый спецификатор указывает, что соответствующий ему параметр в наборе, расположенный справа от оператора форматирования, должен быть представлен целым числом. В табл. 8.1 приведены наиболее полезные спецификаторы форматирования.

Таблица 8.1. Спецификаторы форматирования

Символы

Формат

Строка длиной 1 символ
%s Строка любой длины
%d Целое десятичное число
%u Целое беззнаковое десятичное число
Целое восьмеричное число
%х или U Целое шестнадцатеричное число (в случае %х — в верхнем регистре)
%е, %E, %f, %g, %G Числа с плавающей запятой в различных стилях
%% Символ процента

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

  Оператор форматирования можно использовать в любом месте, где Вам заблагорассудится, а не только после инструкции print. Символы I используются всюду, где происходит форматирование строк. Так, оператор форматирования может быть весьма полезным для обработки данных, вводимых пользователем. Польза его применения состоит в том, что в тех случаях, когда Вы не знаете, к какому типу принадлежат введенные данные, всегда можно воспользоваться спецификатором ' %s', чтобы привести данные любого типа к строке символов. Если переданный аргумент не является строкой, то к нему автоматически будет применена функция str(). Более того, в строку символов будут успешно преобразованы даже те данные, которые нельзя преобразовать с помощью функции str(). Пример такого преобразования показан в листинге 8.10.

Листинг 8.10. Вывод функции как переменной

def myfunc():

  pass

 

z = "Printing myfunc yields '%s'." % (myfunc)

print z

  Обратите внимание, что мы не вызываем функцию myfunc(), а только использовали её имя. Если выполнить в интерпретаторе представленный выше код, то на экран будет выведена строка, подобая этой:

Printing myfunc yields '<function myfunc at 7f74f0>'.

  В приведённой выше строке числа (в шестнадцатеричном представлении), следующие после слова "at", сообщают адрес ячейки, начиная с которой в памяти компьютера располагается функция myfunc(). Обычно это бесполезная информация, но она может очень пригодиться в некоторых ситуациях во время отладки программы. Вопросы отладки не рассматриваются в этой книге, хотя в последней главе Вы найдёте несколько советов по этому поводу. Дело в том, что Python является настолько ясным и понятным языком, что в большинстве случаев не имеет смысла выносить отладку программы как отдельный этап работы над проектом (а именно так и приходится делать, работая, например, с языком С). Сообщения об ошибках интерпретатор показывает по мере того, как Вы их допускаете, и для обнаружения и исправления ошибок часто бывает достаточно внимательно просмотреть подозрительный участок кода. Хотя во время работы над действительно большими проектами этап отладки будет необходим. С дополнительной информацией по этому вопросу можно познакомиться на домашней Web-странице Python по адресу http://www.python.org/.

  Ещё один полезный приём, который Вам пригодится в дальнейшей практике, приведён в листинге 8.11.

Листинг 8.11. Форматирование элементов словаря при выводе на печать

#!C:\PYTHON\PYTHON.EXE

import math

 

d={"pi":math.pi,"e":math.e,

   "pipi":2*math.pi,"ee":2*math.e}

 

print "pi %(pi)s e %(e)s 2pi

      %{pipi)s 2e %(ee)s" % d

 

  Оператор форматирования (%) может работать не только с набором, но и со словарем. Обратите внимание, что переменная типа словарь не заключается в скобки. Для форматирования элементов словаря используется следующий синтаксис: %(ключ)спецификатор

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

Объекты файлов

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

  Совершенно не подозревая об этом, раньше Вы уже использовали так называемые объекты файлов. Это происходило каждый раз, когда применялась инструкция print. При этом использовался объект вывода stdout (ещё одно наследие языка С). Компанию ему составляют ещё два подобных объекта файлов: stdin и stderr. Первый обеспечивает ввод данных по указанной позиции, второй отвечает за вывод сообщений об ошибках. Инструкция print "знает" только, как записывать сообщения в объект stdout. Для использования других объектов необходимо применять иные инструкции. Оказывается, что подобно спискам, объекты файлов обладают методами, которые можно использовать для реализации этих задач. Все три стандартные объекта файлов, stdin, stdout и stderr, находятся в модуле sys. Именно поэтому для их использования необходимо заблаговременно импортировать модуль sys.

  Стандартные объекты файлов автоматически открываются Python при загрузке модуля. Все остальные объекты файлов необходимо открывать перед их использованием. Для этого используется специальная функция ореn(), что очевидно по её названию. Она является встроенной функцией Python и для её использования не нужно импортировать никакие дополнительные модули. Таким образом, создание и открытие пользовательских объектов файлов является встроенным свойством Python.

  Открыть файл очень просто. Оказывается, что значительно сложнее запомнить порядок следования параметров в функции ореп():

х = ореn("test.txt","r")

  Вот и всё, что необходимо сделать, чтобы открыть файл для чтения. Символы, используемые в качестве второго параметра, приведены в табл. 8.2.

Таблица 8.2. Управляющие параметры функции ореn()

Символ

Значение

r, rb Открывает файл для чтения
w, wb Открывает файл для замещения исходных данных

a, ab

Открывает файл для добавления к исходным данным

 

  *Прим. В. Шипкова: также есть режим 'r+' - файл открывается для чтения и записи одновременно.

  Дополнительный символ b сообщает операционной системе (ОС), что файл нужно открыть в двоичном формате. Это имеет значение только для тех ОС, которые по-разному обрабатывают файлы в текстовом и двоичном форматах. (Разница между ними состоит в том, что в текстовых файлах информация представлена текстовыми символами, а в двоичных — машинными командами.) Системы Windows и Мас различают форматы файлов, тогда как для UNIX это совершенно безразлично. Даже если Вы работаете только под управлением UNIX, имеет смысл завести привычку открывать файлы в двоичном формате. Тем самым Вы обеспечите лучшую переносимость своих программ. Основная проблема, возникающая при переходе от системы к системе, — это различная обработка символов разрывов строк. Но разрывы строк присутствуют только в текстовом формате, но не в двоичном. Имеет смысл всегда выяснять, в какой именно ОС были созданы переданные Вам файлы. Также при работе с файлами полезно оставлять за собой контроль над тем, какими символами указываются разрывы строк.

  Объекты файлов владеют небольшим числом методов и лишь некоторые из них действительно будут Вам полезны на данном этапе. Эти методы вместе с пояснениями и примерами их использования перечислены в табл. 8.3. В данной таблице буквой f обозначен объект файла.

Таблица 8.3. Методы объектов файлов

Метод

Пояснение

Пример

f.read() Считывает весь файл в буфер обмена buf = f.read()
f.readline() Считывает в буфер одну строку. В качестве строки подразумевается вся последовательность символов вплоть до символа разрыва строки, характерного для текущей ОС l = f.readline()
f.read(n) Считывает n символов в буфер обмена или весь файл, если он содержит меньше строк с=f.read(1024)
f.write (s) Записывает в файл строку s f.write("Hello, World! \n")
f.writelines (list) Записывает в файл список строк list

list=["Hello, ", "World\n"]

f.writelines(list)

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

Листинг 8.12. Программа readit.py

#!c:\python\python.exe

import sys
if __name__ == "__main__" :
if len(sys.argv)>1:
f=
open(sys.argv[1],"rb")
while 1:
t=f.readline()
if t=="":
break
if t[-1:] == '\n':
t=t[:-1]
if t[-1:] == '\r':
t=t[:-1]
sys.stdout.write(t + '\n')
f.close()

  Эту программу можно использовать в качестве простой утилиты преобразования текстовых файлов. Она считывает входной файл, имя которого указывается в командной строке, и копирует каждую строку в объект stdout. Независимо от того, как завершаются строки исходного файла, каждая строка вывода завершается корректным для текущей операционной системы символом разрыва строки. Обратите внимание, что даже если в файле присутствуют пустые строки, они все равно содержат стандартные для данной ОС символы разрыва строки. И только когда файл будет полностью прочитан, метод readline() возвратит действительно пустую строку ( '').

Инструкции try и except.

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

Листинг 8.13. Выполнение readit.py

C:\Python>python readit.py shootka.txt

Traceback (innermost last):

File "readit.py", line 5, in ?

  f=open(sys.argv[l],"rb")

    IOError: [Errno 2] No such file or directory: 'shootka.txt'

 

C:\Python>

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

Синтаксис инструкции try довольно прост:

try:

  выражения, которые нужно опробовать...

  Обратите внимание, что выражения блока инструкции try должны иметь отступ. Инструкция try не работает сама по себе и обязательно требует присутствия ещё одной инструкции — except. Причем except должна иметь тот же отступ, что и связанная с ней try. Если Вы уже запутались в отступах, посмотрите на листинг 8.14, где показана измененная программа readit.py.

Листинг 8.14. Измененная программа readit.py

#!c:\python\python.exe

import sys

if __name__ == "__main__" :

   if len(sys.argv)>1:

     try:

       f=open(sys.argv[1],"rb")

     except:

       print "No file named %s!" % (sys.argv[l],)

     while 1:

       t=f.readline()

       if t==":

         break

     if t[-1:] == '\n':

       t=t[:-1]

     if t[-1:] == '\r':

       t=t[:-1]

     sys.stdout.write(t + '\n')

     f.close()

  К сожалению, выполнение этой программы не пройдет гладко. Жалоба интерпретатора показана в листинге 8.15.

C:\Python>python readit.py shootka.txt

No file named shootka.txt!

 Traceback (innermost last):

   File "readit.py", line 10, in ?

     t=f.readline()

       NameError: f

 

C:\Python>

  Что же опять произошло? Сначала, как и предполагалось, было выведено наше сообщение об ошибке, но затем Python сообщил ещё об одной ошибке, которую мы никак не ожидали.

  Внимательно просмотрев текст программы, Вы заметите, что в случае сбоя при выполнении блока оператора try, в результате чего и вызывается исключительная ситуация (технический термин, используемый при возникновении ошибки в Python), переменная f никогда не будет создана. Все было бы прекрасно, если бы мы не забыли, что вслед за выводом сообщения об ошибке надо прекратить выполнение программы. В нашем же варианте программа после возникновения ошибки продолжает выполняться дальше, что ведёт к следующей ошибке. Когда происходит вызов метода f.readline(), вдруг обнаруживается, что объекта f нет и в помине. Простое изменение, показанное в листинге 8.16, решит эту проблему.

Листинг 8.16. ещё раз измененная readit.py

#!с:\python\python.exe

import sys

if __name__ == "__main__" :

  if len(sys.argv)>1:

    try:

      f=open(sys.argv[1],"rb")

    except:

      print "No file named %s!" % (sys.argv[1],)

      sys.exit(0)

    while 1:

      t=f.readline()

      if t=='':

        break

    if t[-1:] == '\n':

      t=t[:-1]

    if t[-1:] == '\r':

      t=t[:-1]

    sys.stdout.write(t + '\n')

    f.close()

  Вызов функции sys.exit(n) является общепринятым методом аварийного завершения программ. Если с аргументом n передаётся 0, это означает, что программа завершилась нормально. Любое другое значение сообщает операционной системе, что произошел какой-то сбой. Программисты редко используют вызов sys.exit(0). Если Python до конца прочитывает весь файл программы и больше не встречает ни одной команды, он предполагает, что программа была выполнена корректно, поэтому сам осуществляет вызов sys.exit(0).

  В нашем случае, если пользователь укажет в параметрах программы несуществующее или неправильное имя файла, Вы можете трактовать это либо просто как обычную опечатку, либо как ошибку. Я, например, предпочитаю трактовать подобную ситуацию как опечатку, поэтому вызываю sys.exit(0) вместо sys.exit(-l).

  Исключительные ситуации (или проще — исключения) — это тот мост, который связывает программный продукт в идеале с его реальным воплощением и эксплуатацией пользователями. Для управления исключениями в Python имеются встроенные инструкции try и except, которые не только дают возможность программистам отслеживать ошибки, но и позволяют упростить код программы и сделать его более логичным.

  Вы помните, как в листинге 8.2 у нас возникла проблема из-за того, что string.atoi() была передана строка, символы которой не были цифрами. Когда функция atoi () не получила ожидаемые значения, Python прервал выполнение программы и сообщил об ошибке. Одним из методов корректного завершения работы программы состоит в том, чтобы первым исследовать строку на наличие в ней недопустимых символов. Если таковые будут обнаружены, то следует вывести для пользователя информативное сообщение об ошибке. Очень здорово, но если функция string.atoi() уже сама знает, какие символы ей подходят, а какие нет, то зачем усложнять себе жизнь и создавать конструкции try...except? Ответ простой: хотя бы из уважения к вашим пользователям, которые оценят это. Кроме того, Вы увидите, что код программы станет чётче, понятнее и удобнее для отладки, так как Вы явно укажете в программе места, где могут произойти сбои в работе. И вдобавок — это стиль настоящего профессионального программиста. Если бы профессионал программирования на языке Python писал подббный код, то он выглядел бы так, как показано в листинге 8.17.

Листинг 8.17. Профессионал использовал бы функцию atoi() только так

s = raw_input("Enter a number: ")

try:

  n = string.atoi(s)

except ValueError:

  print "I require numbers, please."

  sys.exit(l)

...продолжение программа...

Функция raw_input() весьма удобна для считывания данных, вводимых пользователем с клавиатуры. Строковый аргумент используется в качестве приглашения-подсказки, с помощью которого на экран можно вывести наводящие инструкции, если в них появится необходимость.

  Синтаксис инструкции except более сложный, чем инструкции try (наверное, потому, что проще не бывает). С помощью этой инструкции можно не только определять тип ошибки, но и использовать любой собственный код для обработки этих ошибок. Например, можно написать код обработки исключительных ситуаций (это код, следующий за предложением except выражение:), который бы отслеживал ошибки при открытии файлов и в зависимости от типа обнаруженной ошибки либо выводил предупреждающее сообщение и продолжал выполнение программы, либо завершал программу. В конструкции try...except можно использовать необязательную инструкцию else, тело которой будет выполняться в том случае, если в блоке try не была обнаружена ни одна ошибка, что также иногда бывает полезно. В тех случаях, когда программист точно знает, какие ошибки могут произойти, вполне подходит упрощенный код обработки исключительных ситуаций, показанный в предыдущем примере.

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

Листинг 8.18. Удаление временных файлов

#!с:\python\python.exe

import os

import tempfile

 

tf = tempfile.mktemp()

try:

  f = open(tf, "wb")

  f.write("I'm a tempfile.\n")

  f.flush()

  f.close()

finally:

  try:

    os.remove(tf)

  except:

    pass

  В данном примере функция mktemp(), взятая из модуля tempfile, возвращает строку символов, которую можно использовать в качестве имени временного файла. Функция mktemp() гарантирует уникальность данного имени в текущем каталоге. Как бы ни был назван временный файл, его обязательно нужно удалить в конце сеанса работы программы. Если же программа не сможет открыть временный файл, нужно сообщить об этом пользователю. Конструкция try с except внутри блока finally как раз и предназначена для обработки подобных ситуаций. Инструкция finally сообщит пользователю об ошибке. Блок except...pass внутри блока finally предназначен для того, чтобы предупредить вывод интерпретатором на экран сообщений о всех других возможных ошибках, например о том, что указанный файл не существует. Слишком много информации может только запутать пользователя. Поэтому программа ограничится одним сообщением, удалит временный файл, если он все же был создан, и тихо уйдет со сцены.

Резюме

  В данной главе мы рассмотрели несколько концептуальных конструкций Python с функциями map(), reduced(), filter() и инструкцией lambda. Вы узнали о способах вывода и форматирования данных на экран, а также об объектах файлов, которые, как оказалось, активно использовались нами и раньше, но были настолько скромными, что не давали о себе знать. Наконец, мы узнали о способах обработки исключений с помощью конструкций try...except...else и try...finally.

  Это последняя глава части I. В следующих семи главах мы подробно изучим объекты и начнём применять на практике кое-что из того, с чем уже успели познакомиться.

Практикум

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

Почему lambda? Откуда появилось это название?

  Гуидо позаимствовал инструкцию lambda из Scheme — диалекта языка Lisp, хорошо структурированного и функционального языка программирования. Термин lambda как абстрактное представление функций, видимо, впервые использовался в статье Алонзо Черча "Формулирование простой теории типов" (Alonzo Church, A Formulation of the Simple Theory of Types", 1940, The Journal of Symbolic Logic). Самое смешное, что этот термин появился в результате типографской ошибки. Первоначально автор статьи для обозначения абстрактной функции использовал символ х со значком ^ сверху. Но значок сместился влево от х и по ошибке, был принят за греческую букву лямбда (л). За эту информацию спасибо Чарльзу Г. Валдману (Charles G. Waldman), Майклу Махони (Michael Mahoney), Рене Грогнард (Rene Grognard) и Максу Хеилперину (Max Hailperin).

  Какие ещё средства программирования и логические конструкции лежат в основе концептуальности языка Python?

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

  Обработка исключений, как видно, действительно является довольно эффективным подходом к программированию. Используют ли другое языки нечто подобное?

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

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

  1. Какие три важных средства программирования лежат в основе концептуальности Python?

    а) map(), reduce(), filter()

    б) compiled, exec(), apply()

    в) map(), reduce(), lambda

    г) lambda, apply(), exec()

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

    а) except Error1 + Error2 + Еrrоr3 ...

    б) except Error1 and Error2 and Еrrоr3 ...

    в) except Error1 or Error2 or Еrrоr3 ...

    г) except Error1 | Error2 | ЕггогЗ ...

  3. Каким методом проще всего преобразовать целые числа в строку символов?

    a) s = str(123)

    6) s = '123'

    в) s = "%s" % (123)

    г) s = string.string(123)

Ответы

  1. a, 6, в и г. Это был вопрос с подвохом. Все перечисленные средства важны для поддержания концептуальности программирования, так что любой ответ будет правильным.

  2. в. Чтобы перехватить несколько разных исключений, следует использовать выражение except Error1 or Error2 or Еrrоr3.... Исключения, не представленные в этом перечне с оператором or, проследуют незамеченными.

  3. а, б или в. Единственный неправильный ответ — string.string(). Такой функции просто не существует, но все остальные средства вполне подойдут для этой цели.

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

  Чтобы узнать больше о спецификаторах форматирования, обратитесь к превосходному справочнику по языку С авторов Харбисона и Стала (Harbison and Steele, С: A Reference Manual). Выдержав в настоящее время четвертое издание, эта книга стала настольной для многих программистов. Хотя речь в ней идёт совсем не о Python, Вам она вполне подойдёт, поскольку идея спецификаторов форматирования была заимствована в Python из С. В этой книге Вы найдёте множество примеров написания выражений, демонстрирующих все возможные сочетания управляющих символов и сопутствующих им атрибутов.

  Попробуйте заново переписать функцию look(), упоминавшуюся в первом разделе этой главы, чтобы включить в неё конструкцию try...except для достижения большей надёжности и логичности кода.

  Полный список всех встроенных исключений Python можно найти на Web-странице по адресу http://www.python.org/doc/current/lib/module-exceptions.htral.