Магистерская программа «Журналистика данных», факультет коммуникаций, медиа и дизайна, НИУ ВШЭ, 2016-17 учебный год.
Рассмотрим простую задачу: мы хотим ввести с клавиатуры вещественное число, прибавить к нему 1 и вывести результат.
x = float(input("Enter some number: "))
print(x + 1)
12.34 13.34
Если пользователь действительно введёт число, то получит правильный ответ. А если введёт не число, а что-то другое?
x = float(input())
print(x + 1)
asdf
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-2-fc09e59e6e6f> in <module>() ----> 1 x = float(input()) 2 print(x + 1) ValueError: could not convert string to float: 'asdf'
Пользователь получает длинное и непонятное сообщение об ошибке. Вообще-то, такие сообщения об ошибках адресованы не пользователю, а программисту. Пользователю хорошо было бы сообщить о том, что он сделал что-то не то, как-то попонятнее. Как это сделать?
В принципе, можно было бы сначала ввести строку, потом проверить, записано ли в этой строке вещественное число, и если нет, то выдать ошибку. Наш код мог бы выглядеть как-то так.
s = float(input())
if not is_float_number(s):
print("Sorry, this is not a number")
else:
x = float(s)
print(x + 1)
Здесь функция is_float_number(s)
должна проверять, записано ли в строку s
вещественное число: если да, то она возвращает True
, а если нет, то `False.
Проблема состоит в том, что реализовать функцию is_float_number()
не так-то легко. Существует очень много способов представлять число с плавающей точкой в виде строки. Например, 12
, 12.34
, +12.34
, 1e2
, +1e-2
— это всё корректные способы записать число с плавающей точкой. И мы, возможно, хотим дать пользователю возможность использовать их все.
Вместо того, чтобы реализовывать функцию is_float_number()
, можно действовать по-другому. Можно попробовать преобразовать строку в число, а если не получится, то «поймать» ошибку и корректно её обработать. Для этого нужно использовать конструкцию try-except
:
try:
x = float(input("Enter some number: "))
print("The following number is", x + 1)
except ValueError:
print("Sorry, this is not a number")
print("Bye")
Enter some number: Hello Sorry, this is not a number Bye
Блок строчек после ключевого слова try
выполняется в специальном режиме: все ошибки, которые возникнут в ходе выполнения этих команд, могут быть перехвачены и обработаны. Это делается в блоке except
. Здесь указано, какого типа ошибки (они назыаются исключениями) мы перехватываем: в данном случае это ValueError
— именно такого типа ошибку мы получаем, когда пытаемся сконвертировать строку, не похожую на вещественное число, в вещественное число. Это название указывается в тексте ошибки, когда она выдаётся пользователю:
ValueError: could not convert string to float: 'asdf'
Итак, когда в блоке try
на строчке x = float(input("Enter some number: "))
возникает ошибка ValueError
, выполнение кода в блоке try
прекращается (до второй строки этого блока мы не дойдём) и мы оказываемся на строке except
. Дальше мы проверяем, что возникла именно та ошибка, которую мы умеем обрабатывать, и выполняем строчку в блоке except
— в данном случае выдаём сообщение об ошибке. Дальше блок except
заканчивается и мы переходим к следующей строке — print("Bye")
— и выполняем её как ни в чём не бывало. Если бы мы не перехватили исключение, то выполнение программы прекратилось бы сразу и до этой строчки мы не дошли бы.
С помощью исключений написать такой код. Пользователь вводит вещественное число
x
и в ответ получает обратное число1/x
. Если введен ноль, пользователь получает сообщение об ошибкеI can't divide by zero!
. Если введена строка, пользователь получает сообщение об ошибкеThis is not a number
.
Обработку случая с делением на ноль можно было бы сделать с помощью if
, но мы хотим также использовать исключения. Для начала выясним, как называется соответствующая ошибка.
1/0
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-10-05c9758a9c21> in <module>() ----> 1 1/0 ZeroDivisionError: division by zero
Видно, что она называется ZeroDivisionError
. Теперь можно написать код.
try:
x = float(input("Enter some number: "))
print("The reciprocal is:", 1/x)
except ValueError:
print("This is not a number.")
except ZeroDivisionError:
print("I can't divide by zero!")
print("That's all")
Enter some number: 12 The reciprocal is: 0.08333333333333333 That's all
У одного и того же блока try
может быть несколько блоков except
. Они проверяются по порядку и выполняется тот, который соответствует реально случившейся ошибке. Выполняется ровно один из блоков except
.
Написать программу, которая запрашивает у пользователя имя файла, после этого открывает этот файл и в предположении, что в этом файле записаны вещественные числа (по одному на каждой строчке), находит сумму всех этих чисел. При этом если на какой-то строчке записано на число, а что-то другое, то эту строчку следует пропустить и напечатать сообщение о том, что мы её пропустили, указав, какую именно строчку пропустили. Если этого файла не существует, следует попросить указать другой файл (и добиваться того, чтобы пользователь указал существующий файл, пока не введёт).
Для начала создадим какой-нибудь файл.
content = """12.2
lalala
3
5.6
haha 23.3
8
"""
f = open("testfile.txt", "w")
print(content, file=f)
f.close()
Проверим, что записалось.
f = open("testfile.txt")
for s in f:
print(s.rstrip())
f.close()
12.2 lalala 3 5.6 haha 23.3 8
Теперь можно решать задачу. Первым действием нам необходимо узнать у пользователя имя файла. Возможно, нам придётся запросить его несколько раз — до тех пор, пока пользователь не введёт имя реально существующего файла. Насколько несговорчивым окажется пользователь, мы заранее не знаем, поэтому не знаем, сколько раз нам придётся переспрашивать. Поэтому нам нужен цикл while
. Проще всего это сделать так.
while True:
filename = input("Enter file name: ")
try:
f = open(filename)
break
except FileNotFoundError:
print("No such file. Try again.")
Enter file name: asd No such file. Try again. Enter file name: testfile.txt
Ошибка, которая возникает, когда мы пытаемся открыть файл, называется FileNotFoundError
. (Чтобы узнать это, можно было попытаться открыть какой-нибудь несуществующий файл и посмотреть, какими словами на нас будет ругаться Python.) Если строчка f = open(filename)
сработает, то мы попадём на следующую строчку break
и выйдем из цикла. Если не сработает, то уйдём в except
и потом вернёмся в начало цикла.
Теперь, когда файл открыт, можно посчитать сумму числе в нём.
summa = 0
for line in f:
try:
number = float(line)
summa += number
except ValueError:
print("String without number skipped: " + line.rstrip())
f.close()
print(summa)
String without number skipped: lalala String without number skipped: String without number skipped: haha 23.3 String without number skipped: String without number skipped: 28.799999999999997
finally
¶Есть ещё одно применение обработки исключений. Иногда бывает, что нам нужно совершить какое-то действие «во что бы то ни стало». Например, если файл был открыт с помощью функции open()
, то его хорошо бы закрыть. Если в процессе обработки файла произошла какая-нибудь ошибка, мы можем не добраться до строчки, в которой файл закрывается. Рассмотрим, например, такой код. Он должен открыть файл, запросить у ползователя число, и записать в файл число на 1 больше введенного.
f = open("filename.txt", "w")
print("Let us begin!", file=f)
number = int(input("Enter some number: "))
print(number + 1, file=f)
f.close()
Если пользователь ввёл строку, не являющуюся числом, то программа не дойдёт до строчки f.close()
и файл останется не закрытым. Это можно привести к проблемам: например, строка Les ut begin!
скорее всего даже не запишется в файл, несмотря на то, что первый print
будет выполнен. (Это связано с некоторыми особенностями работы операционных систем, которых мы сейчас не будем касаться.) Конечно, мы могли бы обработать исключение, которое может возникнуть в фрагменте кода между открытием и закрытием файла, но зачастую мы не можем обработать все возможные исключения: обязательно произойдёт что-то непредвиденное.
Чтобы решить эту проблему, придумали инструкцию final
.
f = open("filename.txt", "w")
try:
print("Let us begin!", file=f)
number = int(input("Enter some number: "))
print(number + 1, file=f)
print("Okay, I did it!")
finally:
print("Something went wrong. Let us clean up.")
f.close()
print("File is closed.")
Enter some number: asdfasf Something went wrong. Let us clean up. File is closed.
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-20-b00cd9d8d7a4> in <module>() 2 try: 3 print("Let us begin!", file=f) ----> 4 number = int(input("Enter some number: ")) 5 print(number + 1, file=f) 6 print("Okay, I did it!") ValueError: invalid literal for int() with base 10: 'asdfasf'
Как видим, в этом случае ошибка обработки пользовательского ввода, хоть и не была «поймана» с помощью except
, не помешала нам закрыть файл.
Впрочем, конкретно для открытия-закрытия файлов есть более простой подход, реализующий примерно такую же логику, как описано. Он основан на использовании ключевого слова with
:
# f = open("filename.txt)
with open("filename.txt") as f:
for line in f:
print(line.rstrip())
print("Is file closed?", f.closed)
print("Out of with")
print("Is file closed?", f.closed)
Let us begin! Is file closed? False Out of with Is file closed? True
Простейший пример класса.
class Student(object):
def __init__(self, name):
self.name = name
self.grades = []
def get_average_grade(self):
return sum(self.grades)/len(self.grades)
def add_grade(self, grade):
self.grades.append(grade)
Ivan = Student("Ivan")
Ivan.name
'Ivan'
Ivan.add_grade(3)
Ivan.grades
[3]
Ivan.add_grade(10)
Ivan.get_average_grade()
6.5
Student
, сделав так, чтобы у студента были отдельные имя и фамилия.has_tails()
, проверяющий, есть ли у студента академические задолженности (то есть оценки ниже 3).partner
, указывающий на супруга студента. Если у студента нет супруга, инициализировать в None
. Добавить метод set_partner
, принимающий на вход другой объект класса Student
и устанавшивающий своё свойство partner
в этот объект и одновременно свойство partner
этого объекта в себя.class Student(object):
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
self.grades = []
self.partner = None
def get_average_grade(self):
return sum(self.grades)/len(self.grades)
def add_grade(self, grade):
self.grades.append(grade)
def has_tails(self):
return min(self.grades) < 3
def set_partner(self, partner):
if self.partner is not None:
self.remove_partner()
if partner.partner is not None:
partner.remove_partner()
self.partner = partner
partner.partner = self
def remove_partner(self):
my_ex = self.partner
self.partner = None
my_ex.partner = None
Mary = Student("Mary", "Smith")
Dan = Student("Dan", "McClaud")
print(Mary.partner)
None
Mary.set_partner(Dan)
Mary.partner.firstname
'Dan'
Dan.partner
<__main__.Student at 0x10d1bf1d0>
Mary.partner.partner.partner.partner.firstname
'Mary'
Ivan = Student("Ivan", "Ivanov")
Ivan.add_grade(2)
if Ivan.has_tails():
print("Let us call Ivan's parents")
Let us call Ivan's parents
Mary = Student("Mary Ann")
Christian = Student("Christian")
yyy = Student("Somebody")
Mary.add_grade(10)
Mary.get_average_grade()
6.0
Mary.add_grade(5)
Mary.add_grade(3)
print(Mary.get_average_grade())
4.857142857142857
yyy.name
'Somebody'