Программирование и научные вычисления на языке Python/§6

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

c2f.py
Traceback (most recent call last):
File"c2f.py", line 2, in
C = float(sys.argv[1])
IndexError: list index out of range

Python прервал выполнение программы, показал что ошибка находится во второй строке и указал на тип ошибки — IndexError и краткое объяснение что не так. Из этой информации, просмотрев код программы, можно сделать вывод, что индекс 1 выходит за пределы списка (index out of range). А это и правильно, ведь в списке sys.argv только нулевой элемент, название программы. Значит есть всего один возможный индекс 0.

Решение громоздкое

править

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

if len(sys.argv) < 2:
    print 'You failed to provide Celsius degrees as input '\
        'on  the  command  line!'
    sys.exit(1)   #  прекращаем ввиду ошибки
F = 9.0*C/5 + 32
print '%gC is %.1fF' % (C, F)


Для преднамеренного прекращения программы используется функция exit модуля sys. В случае прекращения программы без ошибок функции передается 0, в случае наличия ошибки любое отличное от нуля значение (например, 1). Но это решение для такого элегантного языка как Python весьма неповоротливо и громоздко, в нем существует гораздо более приятная конструкция.

Решение элегантное: try-except

править

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

try:
    <statements>
except:
    <statements>

Если что-то вдруг неправильно идет в блоке try, то выполнение программы тут же перескакивает в блок except, в котором находится обезболивающее средство против полученной ошибки.

Перехват исключений

править

Для того, чтобы лучше прояснить технику перехвата исключений мы, конечно, применим ее к нашему примеру:

import sys
try:
    C = float(sys.argv[1])
except:
    print 'You failed to provide Celsius degrees as input '\
          'on the command line!'
    sys.exit(1)

F = 9.0*C/5 + 32
print '%gC is %.1fF' % (C, F)


Теперь, если мы не передадим аргументов, то найти sys.argv[1] невозможно, значит наша кампания потерпела неудачу, возникло исключение и мы отправляемся в блок except. Если же передать аргумент, то выполняется только блок try. Проверим:


c2f.py
You failed to provide Celsius degrees as input on the command line!

c2f.py 21
21C is 69.8F

Особые исключения

править

Возвращаясь к выражению C = float(sys.argv[1]) можно сказать, что вообще здесь видны две чаще всего встречающиеся ошибки: первая, которую мы рассмотрели — отсутствие значения аргумента, вторая — в качестве аргумента вводится строка, которая не может быть конвертирована в float. Python определяет обе эти ошибки и называет их определенными именами: в первом случае это IndexError, во втором случае ValueError. В программе выше мы переходим в общий блок except независимо от того, что происходит неправильно в блоке try. Например, мы не пропустили значение переменной при вызове, но записали его в неверной форме (21С), результат тот же, что и раньше:


c2f.py 21C
You failed to provide Celsius degrees as input on the command line!


В Python есть удобная возможность разделять инструкции для ошибок различного рода:


import sys
try:
    C = float(sys.argv[1])
except IndexError:
    print 'Celsius degrees must be supplied on the command line'
    sys.exit(1)
except ValueError:
    print 'Celsius degrees must be a pure number, '\
          'not  "%s"' % sys.argv[1]
    sys.exit(1)

F = 9.0*C/5 + 32
print '%gC is %.1fF' % (C, F)


Теперь в зависимости от ошибки совершенной пользователем, мы и сами можем сказать пользователю, что он сделал не так и как это исправить.


Типы исключений

править

Использование индекса, выходящего за пределы списка, приводит к ошибке IndexError:

>>> data = [1.0/i for i in range(1,10)]
>>> data[9]
...
IndexError: list index out of range


Некоторые языки программирования (например, Fortran, C, C++ и Perl) позволяют индексацию вне интервала, что может быть иногда удобным, но делает сложным поиск скрытых ошибок. Python всегда останавливает программу, когда встречает неправильный индекс.

В случае, если содержимое строки не представляет собой только число, конвертирование заканчивается неудачей и ошибкой ValueError:

>>> C = float('21 C')
...
ValueError: invalid literal for float(): 21 C


В случае, если вызывается переменная, которой не задано значение, возникает ошибка NameError:

>>> print a
...
NameError: name 'a' is not defined


Деление на ноль:

>>> 3.0/0
...
ZeroDivisionError: float division


В случае, если вы ошибаетесь в написании ключевых слов языка, возникает SyntaxError:

>>> forr d in data:
...
    forr d in data:
         ^
SyntaxError: invalid syntax


Если мы захотим перемножить число на строку:

>>> 'a  string'*3.14
...
TypeError: cant multiply sequence by non-int of type 'float'


Исключение TypeError возникает, поскольку объекты таких типов не могут быть перемножены (str и float). Но, вообще говоря, это не значит что число и строка не могут быть перемножены.


Отступление об умножении

править

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


>>> '--'*10     #  десять двойных дефисов = 20 дефисов
'--------------------'
>>> n  = 4
>>> [1, 2, 3]*n
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> [0]*n
[0, 0, 0, 0]


Возбуждение исключений: raise

править

В случае возникновения ошибки, вы можете, как раньше, вывести сообщение и прекратить программу с помощью sys.exit(1), а можете возбудить исключение. Для этого вы пишите raise E(message), где E известный тип ошибки, а message — строка, объясняющая, что произошло. Чаще всего на месте E стоит ValueError или TypeError. Вы можете и сами определить тип ошибки. И, конечно, пример.


Пример

править

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


def read_C():
    try:
        C = float(sys.argv[1])
    except IndexError:
        raise IndexError('Celsius degrees must be supplied on the command line')
    except  ValueError:
        raise ValueError('Celsius  degrees must be a pure number, '\
                         'not "%s"' % sys.argv[1])
    if C < -273.15:
        raise ValueError('C=%g is a non-physical value!' % C)
    return C


Далее имеются две возможности использовать функцию read_C(). Простой:


C = read_C()


Неправильный ввод приведет к:


c2f.py
Traceback (most recent call last):
File "c2f.py", line 5, in ?
raise IndexError
IndexError: Celsius degrees must be supplied on the command line.


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


try:
    C = read_C()
except Exception, e:
    print e
    sys.exit(1)


Exception это имена всех возможных исключений, e — сообщение исключения. В нашем случае у нас в функции записаны два типа исключений, поэтому:


try:
    C = read_C()
except  (ValueError, IndexError), e:
    print e
    sys.exit(1)


После блока определения функции и блока try-except пишем блок вычислений и проверяем программу:


import sys

def read_C():
    try:
        C = float(sys.argv[1])
    except IndexError:
        raise IndexError\
        ('Celsius degrees must be supplied on the command line')
    except ValueError:
        raise ValueError\
        ('Celsius degrees must be a pure number, '\
         'not "%s"' % sys.argv[1])
    # C is read correctly as a number, but can have wrong value:
    if C < -273.15:
        raise ValueError('C=%g is a non-physical value!' % C)
    return C

try:
    C = read_C()
except (IndexError, ValueError), e:
    print e
    sys.exit(1)
    
F = 9.0*C/5 + 32
print '%gC is %.1fF' % (C, F)


c2f.py
Celsius degrees must be supplied on the command line

c2f.py 21C
Celsius degrees must be a pure number, not "21C"

c2f.py -500
C=-500 is a non-physical value!

c2f.py 21
21C is 69.8F


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

Мельком о графическом интерфейсе

править

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

Но чтобы проиллюстрировать возможности Python и в таком ракурсе, приведем пример нашей программы, выполненной с использованием графического интерфейса (graphical user interface, GUI):


from Tkinter import *
root = Tk()
C_entry = Entry(root, width=4)
C_entry.pack(side='left')
Cunit_label = Label(root, text='Celsius')
Cunit_label.pack(side='left')

def compute():
    C = float(C_entry.get())
    F = (9./5)*C + 32
    F_label.configure(text='%g' % F)

compute = Button(root, text=' is ', command=compute)
compute.pack(side='left', padx=4)

F_label = Label(root, width=4)
F_label.pack(side='left')
Funit_label = Label(root, text='Fahrenheit')
Funit_label.pack(side='left')

root.mainloop()


Цель дальнейшего рассмотрения — показать как в общих чертах устроено программирование GUI. Строится оно из маленьких графических элементов, называемых виджетами (widgets). Окно, что вы получили, если запустили программу выше, включает пять таких виджетов. Слева находится элемент для ввода значений (entry widget), справа — label widget, который показывает некоторый текст, здесь это «Celsius». Также здесь мы видим кнопку, button widget, при нажатии на которой осуществляется расчет и вывод результата. Этот результат показывается в еще одном label widget, справа от кнопки. Наконец, справа от результата мы видим label widget с текстом «Fahrenheit». В программе записано, какие виджеты надо создать и как правильно разместить в окне.

Первая инструкция нашей программы импортирует все инструменты из модуля Tkinter, предназначенного специально для создания GUI. Вначале нам требуется создать основной (root) виджет, который будет содержать окно со всеми остальными виджетами. Этот корневой виджет объект типа Tk. Далее мы создаем entry widget, на который будет ссылаться переменная C_entry. Этот виджет является объектом типа Entry, поддерживаемым модулем Tkinter. Описание виджета происходит так:


variable_name = Widget_type(parent_widget, option1, option2, ...)
variable_name.pack(side='left')


Когда создается новый виджет, мы должны связать его с родительским (parent widget), в котором этот новый элемент будет упакован. В настоящей программе все элементы размещаются в виджете root. Различные виджеты имеют и соответствующие им по сану полномочия. Например, Entry widget может изменять ширину текстовой области, в нашем случае width=4, то есть поле имеет ширину в 4 символа. Важно не забывать «упаковывать» (pack) элементы, иначе они будут невидимы.

Другие виджеты создаются похожим способом. Другая важная особенность заключается в том, как связано нажатие кнопки, на которой написано "is" и расчет внутри программы. Button widget привязан к функции compute через опцию command=compute. Это означает, что когда пользователь нажимает "is", вызывается функция compute. Внутри функции compute сначала с помощью функции get забирается значение из виджета С_Entry, затем в этой же строчке оно преобразуется из строки в float и отправляется вычисляться. Наконец, обновляется ("configure") текст в label widget с именем F_label.

В конце текста мы видим root.mainloop(). Здесь мы видим знакомое слово loop, цикл. И то, что происходит и правда можно считать бесконечным циклом, который ждет пока пользователь не совершит какое-либо понятное ему действие. В нашем примере действие только одно — нажатие на кнопку. Как только мы на нее нажимаем, вызывается функция compute и программа выполняет свою математическую работу.

На этом мы пока завершим наше знакомство с программированием GUI. О библиотеке Tkinter на Викиверситете создан специальный курс. Кроме того, существует и большие сторонние библиотеки: wxPython, PyQt, PyGTK.