Kodomo

Пользователь

Учебная страница курса биоинформатики,
год поступления 2013

Ещё раз про объекты и классы. Работа с исключениями

О пользе объектно-ориентированного подхода. Наследование.

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

Предположим, у нас есть класс Man, заданный следующим образом:

   1 class Man():
   2 
   3     def __init__(self, P_name): #Class constructor
   4         self.name = P_name
   5         print("Here comes " + self.name)
   6 
   7     def talk(self, P_message):
   8         print(self.name + " says: '" + P_message + "'")
   9 
  10     def walk(self):
  11         print(self.name + " walks")

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

   1 class Superman(Man): # This class inherits from Man class.
   2 
   3     def __init__(self, P_name, P_secret_identity):
   4         #super(Superman, self).__init__(P_name)
   5         Man.__init__(self,P_name)
   6         self.secret_identity = P_secret_identity
   7         print("...but his secret identity is '" \
   8         + self.secret_identity + "' and he's a super-hero!")
   9 
  10 
  11     def walk(self, P_super_speed = False):
  12         if (not P_super_speed):
  13             Man.walk(self)
  14         else:
  15             print(self.secret_identity + " run at the speed of light")
  16 
  17     def fly(self):
  18         print(self.secret_identity + " fly up in the sky")
  19 
  20     def x_ray(self):
  21         print(self.secret_identity + " uses his x-ray vision")

Некоторые методы изменяются по сравнению с родительским классом, переопределить их можно двумя способами: обратившись к методу родительского класса

   1     def walk(self, P_super_speed = False):
   2 
   3         if (not P_super_speed):
   4             Man.walk(self)
   5         else:
   6             print(self.secret_identity + " run at the speed of light")

либо с помощью метода super(), который вызывает тот же самый метод у родителя класса:

   1     def walk(self, P_super_speed = False):
   2         if (not P_super_speed):
   3             super(Superman, self).walk(P_name)
   4         else:
   5             print(self.secret_identity + " run at the speed of light")

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

Наследование - еще один пример

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

   1 class Point():
   2         def __init__(self, x, y, color):
   3                 self.x=x
   4                 self.y=y
   5                 self.color = color
   6         def square(self):
   7                 return 0
   8         def getColor(self):
   9                 return self.color
  10         def __str__(self):
  11                 return "point (%d, %d), %s" % (self.x, self.y, self.color)

Метод str - это строковое представление объекта. Если определен этот метод, то когда мы печатаем с помощью print, объект преобразуется по таким правилам в строку для печати. Поскольку точка не имеет размера, ее площадь равна 0. Теперь зададим класс для прямоугольников. У прямоугольника, в отличие от точки, есть ширина и высота.

   1 class Rect(Point):           #в скобках указываем класс, от которого наследуем
   2         def __init__(self, x, y, color, w, h):
   3                 Point.__init__(self, x, y, color)   # вызываем конструктор родительского класса
   4                 self.w = w
   5                 self.h = h
   6         def square(self):
   7                 return self.w*self.h
   8         def __str__(self):
   9                 return "rect (%d, %d), %s, %d, %d" % (self.x, self.y, self.color, self.w, self.h)

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

   1 class Square(Rect):
   2         def __init__(self, x, y, color, w):
   3                 Rect.__init__(self, x, y, color, w, w)
   4         def __str__(self):
   5                 return "qsuare (%d, %d), %s, %d" % (self.x, self.y, self.color, self.w)

Ну и теперь создадим список из разных фигур и выведем на экран:

   1 figures = []
   2 
   3 figures.append(Point(0, 0, "red"))
   4 figures.append(Rect(0, 0, "blue", 1, 2))
   5 figures.append(Square(0, 0, "green", 3))
   6 
   7 for fig in figures:
   8         print fig
   9         print fig.getColor()

Работа с исключениями

Исключение (exceptions) – это особый тип данных, способ обозначить в программе ошибку (например "нам дали плохие данные") таким образом, чтобы потом эту ошибку можно было исправить. Программы и методы в python могут иметь некоторые требования к тому, с какими данными и в каких ситуациях они могут быть применимы, когда эти ожидаемые требования не исполняются, выбрасываются исключения - исключения из общего порядка вещей для работы этих методов. Примеры возможных исключений -

exception ArithmeticError (OverflowError, ZeroDivisionError)

-основной класс для описания арифметических ошибок (результат арифметической операции слишком велик, чтобы быть представленным; деление на ноль)

exception IOError

- ошибки, связанные с потоками ввода – вывода (например методы print, open()), ошибки – не существует файла, нет места на диске для записи

exception ImportEror

- нет модуля с указанным названием для import’а

exception IndexError

- отсутствие элемента с заданным индексом

exception KeyError

- нет в словаре заданного ключа

exception ValueError

   1 >>> float('aaa')
   2 Traceback (most recent call last):
   3   File "<input>", line 1, in <module>
   4 ValueError: could not convert string to float: aaa
   5 
   6 >>> 100/0
   7 Traceback (most recent call last):
   8   File "<input>", line 1, in <module>
   9 ZeroDivisionError: integer division or modulo by zero

При встрече исключительного случая - программа прерывается с выводом сообщение об ошибке или продолжает работать с предупреждением в зависимости от типа ошибки. Предположим, мы хотим написать программу, которая читает информацию из файлов (например, просто считает, сколько строк в каждом файле), имена которых подаются в командной строке. python countLines.py a.txt b.txt c.txt

   1 import sys
   2 args = sys.argv[1:]
   3 if len(args)>0:
   4     for filename in args:
   5         file = open(filename)
   6         print filename, len(file.read().split("\n"))

Допустим, в директории, в которой мы находимся, есть файлы a.txt и c.txt, но нет b.txt. Тогда при попытке открыть несуществующий файл программа упадет, так и не посмотрев третий файл. При этом питон напишет вам сообщение об ошибке, которое помимо прочего будет содержать строчку

   1 Traceback (most recent call last):
   2   File "<input>", line 1, in <module>
   3 IOError: [Errno 2] No such file or directory: 'b.txt'

Выполнение программы прервется и файл c.txt обработан не будет.

Отлов исключений

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

   1 try:
   2             file = open(filename)
   3             print filename, len(file.read().split("\n"))
   4 except Exception:
   5             print "WARNING: Can not open " + filename

Теперь, если внутри блока try было выброшено исключение, то в этот момент питон прекратит исполнять содержимое блока и перейдёт к блоку except. Если исключения не случилось, то в блок except питон не попадёт.

   1 $ python countLines.py a.txt b.txt c.txt
   2 a.txt 22
   3 Could not open file b.txt, ignoring
   4 c.txt 51

С помощью конструкции try..except также можно ловить и обрабатывать исключения определенных типов.

   1 >>> while True:
   2 ...     try:
   3 ...         x = int(raw_input("Please enter a number: "))
   4 ...         break
   5 ...     except ValueError:
   6 ...         print "Oops!  That was no valid number.  Try again..."

Ещё пример:

   1 >>> try:
   2     k = 1 / 0
   3 except ZeroDivisionError:
   4     k = 0

Выброс исключений

Исключения можно не только ловить, но и выбрасывать в заданных ситуациях.

   1 >>> raise NameError('HiThere')
   2 Traceback (most recent call last):
   3   File "<stdin>", line 1, in ?
   4 NameError: HiThere

Файл infile.txt формата:

#description

#another

12 100

34 450

   1 with open(‘infile.txt’) as file:
   2 for line in file:
   3         if len(line.split()) != 2:
   4 raise Exception("More or less data then needed")
   5         start, end = line.split()

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

Конструкция операторов try..except..else – в else определяется то, что должно быть сделано в случае, если ни одно из исключений except не случилось. Оператор finally - то, что под ним будет выполнено в любом из описанных случаях.

   1 >>> def divide(x, y):
   2 ...     try:
   3 ...         result = x / y
   4 ...     except ZeroDivisionError:
   5 ...         print "division by zero!"
   6 ...     else:
   7 ...         print "result is", result
   8 ...     finally:
   9 ...         print "executing finally clause"
  10 
  11 >>> divide(2, 1)
  12 result is 2
  13 executing finally clause
  14 >>> divide(2, 0)
  15 division by zero!
  16 executing finally clause
  17 >>> divide("2", "1")
  18 executing finally clause
  19 Traceback (most recent call last):
  20   File "<stdin>", line 1, in ?
  21   File "<stdin>", line 3, in divide
  22 TypeError: unsupported operand type(s) for /: 'str' and 'str'

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