With open обработка ошибок

Differentiating between the possible origins of exceptions raised from a compound with statement

Differentiating between exceptions that occur in a with statement is tricky because they can originate in different places. Exceptions can be raised from either of the following places (or functions called therein):

  • ContextManager.__init__
  • ContextManager.__enter__
  • the body of the with
  • ContextManager.__exit__

For more details see the documentation about Context Manager Types.

If we want to distinguish between these different cases, just wrapping the with into a try .. except is not sufficient. Consider the following example (using ValueError as an example but of course it could be substituted with any other exception type):

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)

Here the except will catch exceptions originating in all of the four different places and thus does not allow to distinguish between them. If we move the instantiation of the context manager object outside the with, we can distinguish between __init__ and BLOCK / __enter__ / __exit__:

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        with mgr:
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        # At this point we still cannot distinguish between exceptions raised from
        # __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
        pass

Effectively this just helped with the __init__ part but we can add an extra sentinel variable to check whether the body of the with started to execute (i.e. differentiating between __enter__ and the others):

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        entered_body = False
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        else:
            # At this point we know the exception came either from BLOCK or from __exit__
            pass

The tricky part is to differentiate between exceptions originating from BLOCK and __exit__ because an exception that escapes the body of the with will be passed to __exit__ which can decide how to handle it (see the docs). If however __exit__ raises itself, the original exception will be replaced by the new one. To deal with these cases we can add a general except clause in the body of the with to store any potential exception that would have otherwise escaped unnoticed and compare it with the one caught in the outermost except later on — if they are the same this means the origin was BLOCK or otherwise it was __exit__ (in case __exit__ suppresses the exception by returning a true value the outermost except will simply not be executed).

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    entered_body = exc_escaped_from_body = False
    try:
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
            except Exception as err:  # this exception would normally escape without notice
                # we store this exception to check in the outer `except` clause
                # whether it is the same (otherwise it comes from __exit__)
                exc_escaped_from_body = err
                raise  # re-raise since we didn't intend to handle it, just needed to store it
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        elif err is exc_escaped_from_body:
            print('BLOCK raised:', err)
        else:
            print('__exit__ raised:', err)

Alternative approach using the equivalent form mentioned in PEP 343

PEP 343 — The «with» Statement specifies an equivalent «non-with» version of the with statement. Here we can readily wrap the various parts with try ... except and thus differentiate between the different potential error sources:

import sys

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        value = type(mgr).__enter__(mgr)
    except ValueError as err:
        print('__enter__ raised:', err)
    else:
        exit = type(mgr).__exit__
        exc = True
        try:
            try:
                BLOCK
            except TypeError:
                pass
            except:
                exc = False
                try:
                    exit_val = exit(mgr, *sys.exc_info())
                except ValueError as err:
                    print('__exit__ raised:', err)
                else:
                    if not exit_val:
                        raise
        except ValueError as err:
            print('BLOCK raised:', err)
        finally:
            if exc:
                try:
                    exit(mgr, None, None, None)
                except ValueError as err:
                    print('__exit__ raised:', err)

Usually a simpler approach will do just fine

The need for such special exception handling should be quite rare and normally wrapping the whole with in a try ... except block will be sufficient. Especially if the various error sources are indicated by different (custom) exception types (the context managers need to be designed accordingly) we can readily distinguish between them. For example:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...

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

Содержание страницы:
1. Чтение файла 
    1.2. Чтение больших файлов и работа с ними
    1.3. Анализ текста из файла
2. Запись в файл
    2.1. Запись в пустой файл
    2.2. Многострочная запись в файл
    2.3. Присоединение данных к файлу
3. Исключения
    3.1. Блоки try-except
    3.2. Блоки try-except-else
    3.3. Блоки try-except с текстовыми файлами
    3.4. Ошибки без уведомления пользователя

1. Чтение файла в Python

В файлах может содержаться любой объем данных, начиная от небольшого рассказа и до сохранения истории погоды за столетия. Чтение файлов особенно актуально для приложений, предназначенных для анализа данных. Приведем пример простой программы, которая открывает файл и выводит его содержимое на экран. В примере я буду использовать файл с числом «Пи» с точностью до 10 знаков после запятой. Скачать этот файл можно прямо здесь ( pi_10.txt ) или самим создать текстовый файл и сохранить под любым именем. Пример программы, которая открывает файл и выводит содержимое на экран:

with open(‘pi_10.txt’) as file_pi:
    digits = file_pi.read()
print(digits)

Код начинается с ключевого слова with. При использование ключевого слова with используемый файл открывается с помощью функции open(), а закрывается автоматически после завершения блока with и вам не придется в конце вызывать функцию close(). Файлы можно открывать и закрывать явными вызовами open() и close(). Функция open() получает один аргумент — имя открываемого файла, в нашем случае ‘pi_10.txt’. Python ищет указанный файл в каталоге, где хранится файл текущей программы. Функция open() возвращает объект, представляющий файл ‘pi_10.txt’. Python сохраняет этот объект в переменной file_pi .  

После появления объекта, представляющего файл ‘pi_10.txt’, используется метод read(), который читает все содержимое файла и сохраняет его в одной строке в переменной contents. В конце с помощью функции print содержимое выводится на экран. Запустив этот файл, мы получим данные, находящиеся в нашем файле ‘pi_10.txt’.

3.1415926535

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

  •  Относительный путь. 

Относительный путь приказывает Python искать файлы в каталоге, который задается относительно каталога, в котором находится текущий файл программы

with open(‘files/имя_файла.txt’) as file:

  • Абсолютный путь. 

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

file_path = ‘/Users/Desktop/files/имя_файла.txt’
with open(file_path) as file:

С абсолютными путями можно читать файлы из любого каталога вашей системы. 

1.2. Чтение больших файлов на Python и работа с ними

В первом примере был файл с 10 знаками после запятой. Теперь давайте проанализируем файл с миллионом знаков числа «Пи» после запятой. Скачать число «Пи» с миллионом знаков после запятой можно отсюда( ‘pi_1000000.txt’ ). Изменять код из первого примера не придется, просто заменим файл, который должен читать Python. 

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

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679

1000002

Из выходных данных видно, что строка содержит значение «Пи» с точностью до 1 000 000 знаков после запятой. В Python нет никаких ограничений на длину данных, с которыми можно работать, единственное ограничение это объем памяти вашей системы. 

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

with open(‘pi_1000000.txt‘) as file_pi:
    digits = file_pi.read()
birthday = input(«Введите дату дня рождения: «)
if birthday  in digits:
    print(«Ваш день рождение входит в число ‘Пи'»)
else:
    print(«Ваш день рождение не входит в число ‘Пи'»)

Начало программы не изменилось, читаем файл и сохраняем данные в переменной digits. Далее запрашиваем данные от пользователя с помощью функции input и сохраняем в переменную birstday. Затем проверяем вхождение birstday в digits с помощью команды if-else. Запустив несколько раз программу, получим результат:

Введите дату дня рождения: 260786
Ваш день рождение не входит в число ‘Пи’

Введите дату дня рождения: 260884
Ваш день рождение входит в число ‘Пи’

В зависимости от введенных данных мы получили результат вхождения или не вхождения дня рождения в число «Пи»

Важно: Читая данные из текстового файла, Python интерпретирует весь текст как строку. Если вы хотите работать с ним в числовом контексте, то преобразуйте данные в целое число функцией int() или в вещественное число функцией float().

1.3. Анализ текста из файла на Python

Python может анализировать текстовые файлы, содержащие целые книги. Возьмем книгу «Алиса в стране чудес» и попробуем подсчитать количество слов в книге. Текстовый файл с книгой можете скачать здесь(‘ alice ‘) или загрузить любое другое произведение. Напишем простую программу, которая подсчитает количество слов в книге и сколько раз повторяется имя Алиса в книге.

filename = ‘alice.txt’

with open(filename, encoding=’utf-8′) as file:
    contents = file.read()
n_alice = contents.lower().count(‘алиса’)
words = contents.split()
n_words = len(words)

print(f»Книга ‘Алиса в стране чудес’ содержит {n_words} слов.»)
print(f»Имя Алиса повторяется {n_alice} раз.»)

При открытии файла добавился аргумент encoding=’utf-8′. Он необходим, когда кодировка вашей системы не совпадает с кодировкой читаемого файла. После чтения файла, сохраним его в переменной contents.

Для подсчета вхождения слова или выражений в строке можно воспользоваться методом count(), но прежде привести все слова к нижнему регистру функцией lower(). Количество вхождений сохраним в переменной n_alice

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

title = ‘Алиса в стране чудес’
print(title.split())

[‘Алиса’, ‘в’, ‘стране’, ‘чудес’]

После использования метода split(), сохраним список в переменной words и далее подсчитаем количество слов в списке, с помощью функции len(). После подсчета всех данных, выведем на экран результат:

Книга ‘Алиса в стране чудес’ содержит 28389 слов.
Имя Алиса повторяется 419 раз.

2.1. Запись в пустой файл в Python

Самый простой способ сохранения данных, это записать их в файл. Чтобы записать текс в файл, требуется вызвать open() со вторым аргументом, который сообщит Python что требуется записать файл. Пример программы записи простого сообщения в файл на Python:

filename = ‘memory.txt’

with open(filename, ‘w’) as file:
    file.write(«Язык программирования Python»)

Для начала определим название и тип будущего файла и сохраним в переменную filename. Затем при вызове функции open() передадим два аргумента. Первый аргумент содержит имя открываемого файла. Второй аргумент ‘ w ‘ сообщает Python, что файл должен быть открыт в режиме записи. Во второй строчке метод write() используется для записи строки в файл. Открыв файл ‘ memory.txt ‘ вы увидите в нем строку:

Язык программирования Python

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

Важно: Открывая файл в режиме записи ‘ w ‘, если файл уже существует, то Python уничтожит его данные перед возвращением объекта файла.

Файлы можно открывать в режимах:

  • чтение ‘ r ‘
  • запись ‘ w ‘
  • присоединение ‘ a ‘
  • режим как чтения, так и записи ‘ r+ ‘

2.2. Многострочная запись в файл на Python

При использовании функции write() символы новой строки не добавляются в записываемый файл:

filename = ‘memory.txt’

with open(filename, ‘w’) as file:
    file.write(«Язык программирования Python»)
    file.write(«Язык программирования Java»)
    file.write(«Язык программирования Perl»)

В результате открыв файл мы увидим что все строки склеились:

Язык программирования PythonЯзык программирования JavaЯзык программирования Perl

Для написания каждого сообщения с новой строки используйте символ новой строки \n

filename = ‘memory.txt’

with open(filename, ‘w’) as file:
    file.write(«Язык программирования Python\n«)
    file.write(«Язык программирования Java\n«)
    file.write(«Язык программирования Perl\n«)

Результат будет выглядеть так:

Язык программирования Python
Язык программирования Java
Язык программирования Perl

2.3. Присоединение данных к файлу на Python 

Для добавления новых данных в файл, вместо того чтобы постоянно перезаписывать файл, откройте файл в режиме присоединения ‘ a ‘. Все новые строки добавятся в конец файла. Возьмем созданный файл из раздела 2.2 ‘memory.txt’. Добавим в него еще пару строк.

filename = ‘memory.txt’

with open(filename, ‘a’) as file:
    file.write(«Hello world\n»)
    file.write(«Полет на луну\n»)

В результате к нашему файлу добавятся две строки:

Язык программирования Python
Язык программирования Java
Язык программирования Perl
Hello world
Полет на луну

3. Исключения в Python

При выполнении программ могут возникать ошибки, для управления ими Python использует специальные объекты, называемые исключениями. Когда в программу включен код обработки исключения, ваша программа продолжится, а если нет, то программа остановится и выведет трассировку с отчетом об исключении. Исключения обрабатываются в блоках try-except. С блоками try-except программы будут работать даже в том случае, если что-то пошло не так.

3.1. Блоки try-except на Python

Приведем пример простой ошибки деления на ноль:

print(7/0)

Traceback (most recent call last):
  File «example.py», line 1, in <module>
    print(7/0)
ZeroDivisionError: division by zero

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

try:
    print(7/0)
except ZeroDivisionError:
    print(«Деление на ноль запрещено»)

Команда print(7/0) помещена в блок try. Если код в блоке try выполняется успешно, то Python пропускает блок except.  Если же код в блоке try создал ошибку, то Python ищет блок except и запускает код в этом блоке. В нашем случае в блоке except выводится сообщение «Деление на ноль запрещено». При выполнение этого кода пользователь увидит понятное сообщение:

Деление на ноль запрещено

Если за кодом try-except следует другой код, то Python продолжит выполнение программы. 

3.2. Блок try-except-else на Python

Напишем простой калькулятор, который запрашивает данные у пользователя, а затем результат деления выводит на экран. Сразу заключим возможную ошибку деления на ноль  ZeroDivisionError и добавим блок else при успешном выполнение блока try.

while True:
    first_number = input(«Введите первое число: «)
    if first_number == ‘q’:
        break
    second_number = input(«Введите второе число: «)
    if second_number == ‘q’:
        break
    try:
        a = int(first_number) / int(second_number)
    except ZeroDivisionError:
        print(«Деление на ноль запрещено»)
    else:
        print(f»Частное двух чисел равно {a}»)

Программа запрашивает у пользователя первое число (first_number), затем второе (second_number). Если пользователь не ввел » q « для завершения работы программа продолжается. В блок try помещаем код, в котором возможно появление ошибки. В случае отсутствия ошибки деления, выполняется код else и Python выводит результат на экран. В случае ошибки ZeroDivisionError выполняется блок except и выводится сообщение о запрете деления на ноль, а программа продолжит свое выполнение. Запустив код получим такие результаты:

Введите первое число: 30
Введите второе число: 5
Частное двух чисел равно 6.0
Введите первое число: 7
Введите второе число: 0
Деление на ноль запрещено
Введите первое число:  q

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

3.3. Блок  try-except с текстовыми файлами на Python

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

filename = ‘alice_2.txt’

with open(filename, encoding=’utf-8′) as file:
    contents = file.read()

Так как такого файла не существует, Python выдает исключение:

Traceback (most recent call last):
  File «example.py», line 3, in <module>
    with open(filename, encoding=’utf-8′) as file:
FileNotFoundError: [Errno 2] No such file or directory: ‘alice_2.txt’

FileNotFoundError — это ошибка отсутствия запрашиваемого файла. С помощью блока try-except обработаем ее:

filename = ‘alice_2.txt’

try:
    with open(filename, encoding=’utf-8′) as file:
        contents = file.read()
except FileNotFoundError:
    print(f»Запрашиваемый файл {filename } не найден»)

В результате при отсутствии файла мы получим:

Запрашиваемый файл alice_2.txt не найден

3.4. Ошибки без уведомления пользователя

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

ilename = ‘alice_2.txt’

try:
    with open(filename, encoding=’utf-8′) as file:
        contents = file.read()
except FileNotFoundError:
    pass

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

Далее: Функции json. Сохранение данных Python

Назад: Классы в Python

Working with files makes your programs more relevant and usable, also it makes your programs quickly analyze lots of data.

Learning to work with files and save data will make your programs easier for people to use. Users will be able to choose what data to enter and when to enter it. People can run your program, do some work, and then close the program and pick up where they left off later. Learning to handle exceptions will help you deal with situations in which files don’t exist and deal with other problems that can cause your programs to crash. This will make your programs more robust when they encounter bad data, whether it comes from innocent mistakes or from malicious attempts to break your programs. With these skills, you’ll make your programs more applicable, usable, and stable.

Reading from a File

An incredible amount of data is available in text files. Text files can contain weather data, traffic data, socioeconomic data, literary works, and more. Reading from a file is particularly useful in data analysis applications, but it’s also applicable to any situation in which you want to analyze or modify information stored in a file. For example, you can write a program that reads in the contents of a text file and rewrites the file with formatting that allows a browser to display it.

When you want to work with the information in a text file, the first step is to read the file into memory. You can read the entire contents of a file, or you can work through the file one line at a time.

Reading an Entire File

To begin, we need a file with a few lines of text in it. Let’s start with a file that contains pi to 30 decimal places, with 10 decimal places per line, consider we have a text file (pi_digits. txt) in the same directory of your program:

3.1415926535
  8979323846
  2643383279

Enter fullscreen mode

Exit fullscreen mode

Here’s a program that opens this file, reads it, and prints the contents of the file to the screen:

with open('pi_digits.txt') as file_object:
    contents = file_object.read()

print(contents)

Enter fullscreen mode

Exit fullscreen mode

The first line of this program with open('pi_digits.txt') as file_object: has a lot going on. Let’s start by looking at the open() function. To do any work with a file, even just printing its contents, you first need to open the file to access it. The open() function needs one argument: the name of the file you want to open. Python looks for this file in the directory where the program that’s currently being executed is stored.

Here, open('pi_digits.txt') returns an object representing pi_digits.txt. Python assigns this object to file_object, which we’ll work with later in the program.

The keyword with closes the file once access to it is no longer needed. Notice how we call open() in this program but not close(). You could open and close the file by calling open() and close(), but if a bug in your program prevents the close() method from being executed, the file may never close. This may seem trivial, but improperly closed files can cause data to be lost or corrupted. And if you call close() too early in your program, you’ll find yourself trying to work with a closed file (a file you can’t access), which leads to more errors. It’s not always easy to know exactly when you should close a file, but with the structure shown here, Python will figure that out for you. All you have to do is open the file and work with it as desired, trusting that Python will close it automatically when the with block finishes execution.

Once we have a file object representing pi_digits.txt, we use the read() method in the second line of our program contents = file_object.read() to read the entire contents of the file and store it as one long string in contents. When we print the value of contents, we get the entire text file back.

If you find a blank line appears at the end of your output that’s because read() returns an empty string when it reaches the end of the file; this empty string shows up as a blank line. If you want to remove the extra blank line, you can use rstrip() in the call to print().

print(contents.rstrip())

Enter fullscreen mode

Exit fullscreen mode

File Paths

When you pass a simple filename like pi_digits.txt to the open() function, Python looks in the directory where the file that’s currently being executed (that is, your . py program file) is stored.

Sometimes, depending on how you organize your work, the file you want to open won’t be in the same directory as your program file. For example, you might store your program files in a folder called python_work; inside python_work, you might have another folder called text_files to distinguish your program files from the text files they’re manipulating. Even though text_files is in python_work, just passing open()*the name of a file in *text_files won’t work, because Python will only look in python_work and stop there; it won’t go on and look in text_files.

To get Python to open files from a directory other than the one where your program file is stored, you need to provide a file path, which tells Python to look in a specific location on your system.

Because text_files is inside python_work, you could use a relative file path to open a file from text_files. A relative file path tells Python to look for a given location relative to the directory where the currently running program file is stored. For example, you’d write:

with open('text_files/filename.txt') as file_object:

Enter fullscreen mode

Exit fullscreen mode

This line tells Python to look for the desired . txt file in the folder text_files and assumes that text_files is located inside python_work.

Please note that, Windows systems use a backslash ( \ ) instead of a forward slash ( / ) when displaying file paths, but you can still use forward slashes in your code.

You can also tell Python exactly where the file is on your computer regardless of where the program that’s being executed is stored. This is called an absolute file path. You use an absolute path if a relative path doesn’t work. For instance, if you’ve put text_files in some folder other than python_work—say, a folder called other_files—then just passing open() the path ‘text_files/filename.txt’ won’t work because Python will only look for that location inside python_work. You’ll need to write out a full path to clarify where you want Python to look.

Absolute paths are usually longer than relative paths, so it’s helpful to assign them to a variable and then pass that variable to open():

file_path = '/home/ahmed/other_files/text_files/filename.txt'
with open(file_path) as file_object:

Enter fullscreen mode

Exit fullscreen mode

Using absolute paths, you can read files from any location on your system. For now it’s easiest to store files in the same directory as your program files or in a folder such as text_files within the directory that stores your program files.

Please note that, if you try to use backslashes in a file path, you’ll get an error because the backslash is used to escape characters in strings. For example, in the path C:\path\to\file.txt, the sequence \t is interpreted as a tab. If you need to use backslashes, you can escape each one in the path, like this: C:\\path\\to\\file.txt.

Reading Line by Line

When you’re reading a file, you’ll often want to examine each line of the file. You might be looking for certain information in the file, or you might want to modify the text in the file in some way. For example, you might want to read through a file of weather data and work with any line that includes the word sunny in the description of that day’s weather. In a news report, you might look for any line with the tag <headline> and rewrite that line with a specific kind of formatting.

You can use a for loop on the file object to examine each line from a file one at a time:

file_name = 'pi_digits.txt'

with open(file_name) as file_object:
    for line in file_object:
        print(line)

Enter fullscreen mode

Exit fullscreen mode

3.1415926535

  8979323846

  2643383279

Enter fullscreen mode

Exit fullscreen mode

At file_name = 'pi_digits.txt' we assign the name of the file we’re reading from to the variable filename. This is a common convention when working with files. Because the variable filename doesn’t represent the actual file—it’s just a string telling Python where to find the file—you can easily swap out ‘pi_digits. txt’ for the name of another file you want to work with.

After we call open(), an object representing the file and its contents is assigned to the variable
file_object. We again use the with syntax to let Python open and close the file properly. To examine the file’s contents, we work through each line in the file by looping over the file object.

When we print each line, we find even more blank lines. These blank lines appear because an invisible newline character is at the end of each line in the text file. The print function adds its own newline each time we call it, so we end up with two newline characters at the end of each line: one from the file and one from print(). Using rstrip() on each line in the print() call eliminates these extra blank lines.

file_name = 'pi_digits.txt'

with open(file_name) as file_object:
    for line in file_object:
        print(line.rstrip())

Enter fullscreen mode

Exit fullscreen mode

3.1415926535
  8979323846
  2643383279

Enter fullscreen mode

Exit fullscreen mode

Making a List of Lines from a File

When you use with, the file object returned by open() is only available inside the with block that contains it. If you want to retain access to a file’s contents outside the with block, you can store the file’s lines in a list inside the block and then work with that list. You can process parts of the file immediately and postpone some processing for later in the program.

The following example stores the lines of pi_digits. txt in a list inside the with block and then prints the lines outside the with block:

file_name = 'pi_digits.txt'

with open(file_name) as object_file:
    lines = object_file.readlines()

for line in lines:
    print(line.rstrip())

Enter fullscreen mode

Exit fullscreen mode

3.1415926535
  8979323846
  2643383279

Enter fullscreen mode

Exit fullscreen mode

At lines = object_file.readlines() the readlines() method takes each line from the file and stores it in a list. This list is then assigned to lines, which we can continue to work with after the with block ends.

At for line in lines: we use a simple for loop to print each line from lines. Because each item in lines corresponds to each line in the file, the output matches the contents of the file exactly.

Working with a File’s Contents

After you’ve read a file into memory, you can do whatever you want with that data, so let’s briefly explore the digits of pi. First, we’ll attempt to build a single string containing all the digits in the file with no whitespace in it:

file_name = 'pi_digits.txt'

with open(file_name) as object_file:
    lines = object_file.readlines()

pi_string = ''
for line in lines:
    pi_string += line.rstrip()

print(pi_string)
print(len(pi_string))

Enter fullscreen mode

Exit fullscreen mode

3.1415926535  8979323846  2643383279
36

Enter fullscreen mode

Exit fullscreen mode

We start by opening the file and storing each line of digits in a list, just as we did in the previous example. At pi_string = '' we create a variable, pi_string, to hold the digits of pi. We then create a loop that adds each line of digits to pi_string and removes the newline character from each line pi_string += line.rstrip().

The variable pi_string contains the whitespace that was on the left side of the digits in each line, but we can get rid of that by using strip() instead of rstrip():

--snip--

for line in lines:
    pi_string += line.strip()

print(pi_string)
print(len(pi_string))

Enter fullscreen mode

Exit fullscreen mode

3.141592653589793238462643383279
32

Enter fullscreen mode

Exit fullscreen mode

Now we have a string containing pi to 30 decimal places. The string is 32 characters long because it also includes the leading 3 and a decimal point.

Please note that, When Python reads from a text file, it interprets all text in the file as a string. If you read in a number and want to work with that value in a numerical context, you’ll have to convert it to an integer using the int() function or convert it to a float using the float() function.

Large Files: One Million Digits

So far we’ve focused on analyzing a text file that contains only three lines, but the code in these examples would work just as well on much larger files. If we start with a text file that contains pi to 1,000,000 decimal places instead of just 30, we can create a single string containing all these digits. We don’t need to change our program at all except to pass it a different file. We’ll also print just the first 50 decimal places, so we don’t have to watch a million digits scroll by in the terminal:

filename = 'pi_million_digits.txt'

with open(filename) as file_object:
    lines = file_object.readlines()

pi_string = ''
for line in lines:
    pi_string += line.strip()

print(f"{pi_string[:52]}...")
print(len(pi_string))

Enter fullscreen mode

Exit fullscreen mode

3.14159265358979323846264338327950288419716939937510...
1000002

Enter fullscreen mode

Exit fullscreen mode

The output shows that we do indeed have a string containing pi to 1,000,000 decimal places.

Python has no inherent limit to how much data you can work with; you can work with as much data as your system’s memory can handle.

Is Your Birthday Contained in Pi?

Let’s use the program we just wrote to find out if someone’s birthday appears anywhere in the first million digits of pi. We can do this by expressing each birthday as a string of digits and seeing if that string appears anywhere in pi_string:

filename = 'pi_digits.txt'

with open(filename) as object_file:
    lines = object_file.readlines()

pi_string = ''
for line in lines:
    pi_string += line.strip()

birthday = input("Enter your birthday, in the form mmddyy: ")
if birthday in pi_string:
    print("Your birthday appears in the first million digits of pi!")
else:
    print("Your birthday does not appear in the first million digits of pi.")

Enter fullscreen mode

Exit fullscreen mode

Once you’ve read from a file, you can analyze its contents in just about any way you can imagine.

Writing to a File

One of the simplest ways to save data is to write it to a file. When you write text to a file, the output will still be available after you close the terminal containing your program’s output. You can examine output after a program finishes running, and you can share the output files with others as well. You can also write programs that read the text back into memory and work with it again later.

Writing to an Empty File

To write text to a file, you need to call open() with a second argument telling Python that you want to write to the file. To see how this works, let’s write a simple message and store it in a file instead of printing it to the screen:

filename = 'programming.txt'

with open(filename, 'w') as file_object:
    file_object.write("I Love Programming.")

Enter fullscreen mode

Exit fullscreen mode

The call to open() in this example has two arguments. The first argument is still the name of the file we want to open. The second argument, ‘w’, tells Python that we want to open the file in write mode.

You can open a file in:

  • read mode (‘r’)
  • write mode (‘w’)
  • append mode (‘a’)
  • a mode that allows you to read and write to the file (‘r+’)

If you omit the mode argument, Python opens the file in read-only mode by default.

The open() function automatically creates the file you’re writing to if it doesn’t already exist. However, be careful opening a file in write mode (‘w’) because if the file does exist, Python will erase the contents of the file before returning the file object.

At file_object.write("I Love Programming.") we use the write() method on the file object to write a string to the file. This program has no terminal output, but if you open the file (programming. txt), you’ll see one line.

I love programming.

Enter fullscreen mode

Exit fullscreen mode

This file behaves like any other file on your computer. You can open it, write new text in it, copy from it, paste to it, and so forth.

Python can only write strings to a text file. If you want to store numerical data in a text file, you’ll have to convert the data to string format first using the str() function.

Writing Multiple Lines

The write() function doesn’t add any newlines to the text you write. So if you write more than one line without including newline characters, your file may not look the way you want it to:

filename = 'programming.txt'

with open(filename, 'w') as file_object:
    file_object.write("I love programming.")
    file_object.write("I love creating new games.")

Enter fullscreen mode

Exit fullscreen mode

If you open (programming. txt), you’ll see the two lines squished together:

I love programming.I love creating new games.

Enter fullscreen mode

Exit fullscreen mode

Including newlines in your calls to write() makes each string appear on its own line:

filename = 'programming.txt'

with open(filename, 'w') as file_object:
    file_object.write("I Love Programming.\n")
    file_object.write("I love creating new games.\n")

Enter fullscreen mode

Exit fullscreen mode

I love programming.
I love creating new games.

Enter fullscreen mode

Exit fullscreen mode

You can also use spaces, tab characters, and blank lines to format your output, just as you’ve been doing with terminal-based output.

Appending to a File

If you want to add content to a file instead of writing over existing content, you can open the file in append mode. When you open a file in append mode, Python doesn’t erase the contents of the file before returning the file object. Any lines you write to the file will be added at the end of the file. If the file doesn’t exist yet, Python will create an empty file for you.

Let’s modify last program by adding some new reasons we love programming to the existing file (programming. txt):

filename = 'programming.txt'

with open(filename, 'a') as file_object:
    file_object.write("I also love finding meaning in large datasets.\n")
    file_object.write("I love creating apps that can run in a browser.\n")

Enter fullscreen mode

Exit fullscreen mode

At with open(filename, 'a') as file_object: we use the ‘a’ argument to open the file for appending rather
than writing over the existing file. Then we write two new lines, which are added to (programming. txt).

I love programming.
I love creating new games.
I also love finding meaning in large datasets.
I love creating apps that can run in a browser.

Enter fullscreen mode

Exit fullscreen mode

We end up with the original contents of the file, followed by the new content we just added.


Exceptions

Python uses special objects called exceptions to manage errors that arise during a program’s execution. Whenever an error occurs that makes Python unsure what to do next, it creates an exception object. If you write code that handles the exception, the program will continue running. If you don’t handle the exception, the program will halt and show a traceback, which includes a report of the exception that was raised.

Exceptions are handled with try-except blocks. A try-except block asks Python to do something, but it also tells Python what to do if an exception is raised. When you use try-except blocks, your programs will continue running even if things start to go wrong. Instead of tracebacks, which can be confusing for users to read, users will see friendly error messages that you write.

Handling the ZeroDivisionError Exception

Let’s look at a simple error that causes Python to raise an exception. You probably know that it’s impossible to divide a number by zero, but let’s ask Python to do it anyway:

print(5/0)

Enter fullscreen mode

Exit fullscreen mode

ZeroDivisionError: division by zero

Enter fullscreen mode

Exit fullscreen mode

Of course Python can’t do this, so we get a traceback. The error reported in the traceback ZeroDivisionError, is an exception object. Python creates this kind of object in response to a situation where it can’t do what we ask it to. When this happens, Python stops the program and tells us the kind of exception that was raised. We can use this information to modify our program. We’ll tell Python what to do when this kind of exception occurs; that way, if it happens again, we’re prepared.

Using try-except Blocks

When you think an error may occur, you can write a try-except block to handle the exception that might be raised. You tell Python to try running some code, and you tell it what to do if the code results in a particular kind of exception.

Here’s what a try-except block for handling the ZeroDivisionError exception looks like:

try:
    print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")

Enter fullscreen mode

Exit fullscreen mode

We put print(5/0), the line that caused the error, inside a try block. If the code in a try block works, Python skips over the except block. If the code in the try block causes an error, Python looks for an except block whose error matches the one that was raised and runs the code in that block.

In this example, the code in the try block produces a ZeroDivisionError, so Python looks for an except block telling it how to respond. Python then runs the code in that block, and the user sees a friendly error message instead of a traceback:

You can't divide by zero!

Enter fullscreen mode

Exit fullscreen mode

If more code followed the try-except block, the program would continue running because we told Python how to handle the error. Let’s look at an example where catching an error can allow a program to continue running.

Using Exceptions to Prevent Crashes

Handling errors correctly is especially important when the program has more work to do after the error occurs. This happens often in programs that prompt users for input. If the program responds to invalid input appropriately, it can prompt for more valid input instead of crashing.

Let’s create a simple calculator that does only division:

while True:
    first_number = input("\nFirst Number: ")
    if first_number == 'q':
        break

    second_number = input("Second Number: ")
    if second_number == 'q':
        break

    answer = int(first_number) / int(second_number)
    print(answer)

Enter fullscreen mode

Exit fullscreen mode

This program prompts the user to input a first_number and, if the user does not enter ‘q’ to quit, a second_number. We then divide these two numbers to get an answer w. This program does nothing to handle errors, so asking it to divide by zero causes it to crash:

Give me two numbers, and I'll divide them.
Enter 'q' to quit.

First Number: 10
Second Number: 5
2.0

First Number: 5
Second Number: 0
Traceback (most recent call last):
  File "division_calculator.py", line 13, in <module>
    answer = int(first_number) / int(second_number)
ZeroDivisionError: division by zero

Enter fullscreen mode

Exit fullscreen mode

It’s bad that the program crashed, but it’s also not a good idea to let users see tracebacks. Nontechnical users will be confused by them, and in a malicious setting, attackers will learn more than you want them to know from a traceback. For example, they’ll know the name of your program file, and they’ll see a part of your code that isn’t working properly. A skilled attacker can sometimes use this information to determine which kind of attacks to use against your code.

The else Block

We can make this program more error resistant by wrapping the line that might produce errors in a try-except block. The error occurs on the line that performs the division, so that’s where we’ll put the try-except block. This example also includes an else block. Any code that depends on the try block executing successfully goes in the else block:

print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
    first_number = input("\nFirst Number: ")
    if first_number == 'q':
        break

    second_number = input("Second Number: ")
    if second_number == 'q':
        break

    try:
        answer = int(first_number) / int(second_number)
    except ZeroDivisionError:
        print("You can't divide by 0!")
    else:
        print(answer)

Enter fullscreen mode

Exit fullscreen mode

Give me two numbers, and I'll divide them.
Enter 'q' to quit.

First Number: 10
Second Number: 5
2.0

First Number: 5
Second Number: 0
You can't divide by 0!

First Number: q

Enter fullscreen mode

Exit fullscreen mode

We ask Python to try to complete the division operation in a try block, which includes only the code that might cause an error. Any code that depends on the try block succeeding is added to the else block. In this case if the division operation is successful, we use the else block to print the result.

The except block tells Python how to respond when a ZeroDivisionError arises. If the try block doesn’t succeed because of a division by zero error, we print a friendly message telling the user how to avoid this kind of error. The program continues to run, and the user never sees a traceback.

The try-except-else block works like this: Python attempts to run the code in the try block. The only code that should go in a try block is code that might cause an exception to be raised. Sometimes you’ll have additional code that should run only if the try block was successful; this code goes in the else block. The except block tells Python what to do in case a certain exception arises when it tries to run the code in the try block.

By anticipating likely sources of errors, you can write robust programs that continue to run even when they encounter invalid data and missing resources. Your code will be resistant to innocent user mistakes and malicious attacks.

Handling the FileNotFoundError Exception

One common issue when working with files is handling missing files. The file you’re looking for might be in a different location, the filename may be misspelled, or the file may not exist at all. You can handle all of these situations in a straightforward way with a try-except block.

Let’s try to read a file that doesn’t exist. The following program tries to read in the contents of A*lice in Wonderland*, but we haven’t saved the file (alice. txt) in the same directory as (alice. py):

filename = 'alice.txt'

with open(filename, encoding='utf-8') as f:
    contents = f.read()

Enter fullscreen mode

Exit fullscreen mode

There are two changes here. One is the use of the variable f to represent the file object, which is a common convention. The second is the use of the encoding argument. This argument is needed when your system’s default encoding doesn’t match the encoding of the file that’s being read.

Python can’t read from a missing file, so it raises an exception:

Traceback (most recent call last):
File "alice.py", line 3, in <module>
with open(filename, encoding='utf-8') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'

Enter fullscreen mode

Exit fullscreen mode

The last line of the traceback reports a FileNotFoundError: this is the exception Python creates when it can’t find the file it’s trying to open.

In this example, the open() function produces the error, so to handle it, the try block will begin with the line that contains open():

filename = 'alice.txt'

try:
    with open(filename, encoding='utf-8') as f:
        contents = f.read()
except FileNotFoundError:
    print(f"Sorry, the file {filename} does not exist.")

Enter fullscreen mode

Exit fullscreen mode

In this example, the code in the try block produces a FileNotFoundError, so Python looks for an except block that matches that error. Python then runs the code in that block, and the result is a friendly error message instead of a traceback:

Sorry, the file alice.txt does not exist.

Enter fullscreen mode

Exit fullscreen mode

The program has nothing more to do if the file doesn’t exist, so the error-handling code doesn’t add much to this program. Let’s build on this example and see how exception handling can help when you’re working with more than one file.

Analyzing Text

You can analyze text files containing entire books. Many classic works of literature are available as simple text files because they are in the public domain. The texts used in this section come from Project Gutenberg (http://gutenberg.org/). Project Gutenberg maintains a collection of literary works that are available in the public domain, and it’s a great resource if you’re interested in working with literary texts in your programming projects.

Let’s pull in the text of Alice in Wonderland and try to count the number of words in the text. We’ll use the string method split(), which can build a list of words from a string. Here’s what split() does with a string containing just the title «Alice in Wonderland»:

>>> title = "Alice in Wonderland"
>>> title.split()
['Alice', 'in', 'Wonderland']

Enter fullscreen mode

Exit fullscreen mode

The split() method separates a string into parts wherever it finds a space and stores all the parts of the string in a list. The result is a list of words from the string, although some punctuation may also appear with some of the words. To count the number of words in Alice in Wonderland, we’ll use split() on the entire text. Then we’ll count the items in the list to get a rough idea of the number of words in the text:

filename = 'alice.txt'

try:
    with open(filename, encoding='utf-8') as f:
        contents = f.read()
except FileNotFoundError:
    print(f"Sorry, the file {filename} does not exist.")
else:
    # Count the approximate number of words in the file.
    words = contents.split()
    num_words = len(words)
    print(f"The file {filename} has about {num_words} words.")

Enter fullscreen mode

Exit fullscreen mode

Move the file (alice. txt) to the correct directory, so the try block will work this time. At words = contents.split() we take the string contents, which now contains the entire text of Alice in Wonderland as one long string, and use the split() method to produce a list of all the words in the book. When we use len() on this list to examine its length, we get a good approximation of the number of words in the original string num_words = len(words).

Then we print a statement that reports how many words were found in the file. This code is placed in the else block because it will work only if the code in the try block was executed successfully. The output tells us how many words are in (alice. txt):

The file alice.txt has about 29465 words.

Enter fullscreen mode

Exit fullscreen mode

The count is a little high because extra information is provided by the publisher in the text file used here, but it’s a good approximation of the length of Alice in Wonderland.

Working with Multiple Files

Let’s add more books to analyze. But before we do, let’s move the bulk of this program to a function called count_words(). By doing so, it will be easier to run the analysis for multiple books:

def count_words(filename):
    """Count the approximate number of words in a file."""
    try:
        with open(filename, encoding='utf-8') as f:
            contents = f.read()
    except FileNotFoundError:
        print(f"Sorry, the file {filename} does not exist.")
    else:
        words = contents.split()
        num_words = len(words)
        print(f"The file {filename} has about {num_words} words.")

Enter fullscreen mode

Exit fullscreen mode

Most of this code is unchanged. We simply indented it and moved it into the body of count_words(). It’s a good habit to keep comments up to date when you’re modifying a program, so we changed the comment to a docstring and reworded it slightly.

Now we can write a simple loop to count the words in any text we want to analyze. We do this by storing the names of the files we want to analyze in a list, and then we call count_words() for each file in the list.

def count_words(filename):
    --snip--

filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
    count_words(filename)

Enter fullscreen mode

Exit fullscreen mode

The file alice.txt has about 29465 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215830 words.
The file little_women.txt has about 189079 words.

Enter fullscreen mode

Exit fullscreen mode

The siddhartha. txt file is missed from the directory but it has no effect on the rest of the program’s execution.

Using the try-except block in this example provides two significant advantages:

  • We prevent our users from seeing a traceback.
  • we let the program continue analyzing the texts it’s able to find.

If we don’t catch the FileNotFoundError that ‘siddhartha. txt’ raised, the user would see a full traceback, and the program would stop running after trying to analyze Siddhartha. It would never analyze Moby Dick or Little Women.

Failing Silently

In the previous example, we informed our users that one of the files was unavailable. But you don’t need to report every exception you catch. Sometimes you’ll want the program to fail silently when an exception occurs and continue on as if nothing happened. To make a program fail silently, you write a try block as usual, but you explicitly tell Python to do nothing in the except block. Python has a pass statement that tells it to do nothing in a block:

def count_words(filename):
"""Count the approximate number of words in a file."""
try:
    --snip--
except FileNotFoundError:
    pass
else:
    --snip--

filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
    count_words(filename)

Enter fullscreen mode

Exit fullscreen mode

The only difference between this listing and the previous one is the pass statement. Now when a FileNotFoundError is raised, the code in the except block runs, but nothing happens. No traceback is produced, and there’s no output in response to the error that was raised. Users see the word counts for each file that exists, but they don’t see any indication that a file wasn’t found.

The file alice.txt has about 29465 words.
The file moby_dick.txt has about 215830 words.
The file little_women.txt has about 189079 words.

Enter fullscreen mode

Exit fullscreen mode

The pass statement also acts as a placeholder. It’s a reminder that you’re choosing to do nothing at a specific point in your program’s execution and that you might want to do something there later. For example, in this program we might decide to write any missing filenames to a file called (missing_files. txt). Our users wouldn’t see this file, but we’d be able to read the file and deal with any missing texts.

Deciding Which Errors to Report

How do you know when to report an error to your users and when to fail silently? If users know which texts are supposed to be analyzed, they might appreciate a message informing them why some texts were not analyzed. If users expect to see some results but don’t know which books are supposed to be analyzed, they might not need to know that some texts were unavailable. Giving users information they aren’t looking for can decrease the usability of your program. Python’s error-handling structures give you fine-grained control over how much to share with users when things go wrong; it’s up to you to decide how much information to share.

Well-written, properly tested code is not very prone to internal errors, such as syntax or logical errors. But every time your program depends on something external, such as user input, the existence of a file, or the availability of a network connection, there is a possibility of an exception being raised. A little experience will help you know where to include exception handling blocks in your program and how much to report to users about errors that arise.


Storing Data

Many of your programs will ask users to input certain kinds of information. You might allow users to store preferences in a game or provide data for a visualization. Whatever the focus of your program is, you’ll store the information users provide in data structures such as lists and dictionaries. When users close a program, you’ll almost always want to save the information they entered. A simple way to do this involves storing your data using the json module.

The json module allows you to dump simple Python data structures into a file and load the data from that file the next time the program runs. You can also use json to share data between different Python programs. Even better, the JSON data format is not specific to Python, so you can share data you store in the JSON format with people who work in many other programming languages. It’s a useful and portable format, and it’s easy to learn.

The **JSON* (JavaScript Object Notation) format was originally developed for JavaScript. However, it has since become a common format used by many languages, including Python.*

Using json.dump() and json.load()

Let’s write a short program that stores a set of numbers and another program that reads these numbers back into memory. The first program will use json.dump() to store the set of numbers, and the second program will use json.load().

The json.dump() function takes two arguments: a piece of data to store and a file object it can use to store the data. Here’s how you can use json.dump() to store a list of numbers:

import json

numbers = [1, 2, 3, 4, 5, 10, 11, 15]

filename = 'numbers.json'
with open(filename, 'w') as f:
    json.dump(numbers, f)

Enter fullscreen mode

Exit fullscreen mode

We first import the json module and then create a list of numbers to work with. At filename = 'numbers.json' we choose a filename in which to store the list of numbers. It’s customary to use the file extension .json to indicate that the data in the file is stored in the JSON format. Then we open the file in write mode, which allows json to write the data to the file. At json.dump(numbers, f) we use the json.dump() function to store the list numbers in the file (numbers. json).

This program has no output, but let’s open the file (numbers. json) and look at it. The data is stored in a format that looks just like Python:

[1, 2, 3, 4, 5, 10, 11, 15]

Enter fullscreen mode

Exit fullscreen mode

Now we’ll write a program that uses json.load() to read the list back into memory:

import json

filename = 'numbers.json'
with open(filename) as f:
    numbers = json.load(f)

print(numbers)

Enter fullscreen mode

Exit fullscreen mode

[1, 2, 3, 4, 5, 10, 11, 15]

Enter fullscreen mode

Exit fullscreen mode

At filename = 'numbers.json' we make sure to read from the same file we wrote to. This time when we open the file, we open it in read mode because Python only needs to read from the file. At numbers = json.load(f) we use the json.load() function to load the information stored in (numbers. json), and we assign it to the variable numbers. Finally we print the recovered list of numbers and see that it’s the same list created before.

This is a simple way to share data between two programs.

Saving and Reading User-Generated Data

Saving data with json is useful when you’re working with user-generated data, because if you don’t store your user’s information somehow, you’ll lose it when the program stops running. Let’s look at an example where we prompt the user for their name the first time they run a program and then remember their name when they run the program again.

Let’s start by storing the user’s name:

import json

username = input("What is you name? ")

filename = 'username.json'
with open(filename, 'w') as f:
    json.dump(username, f)
    print(f"We'll remember you when you come back, {username}!")

Enter fullscreen mode

Exit fullscreen mode

What is you name? Ahmed
We'll remember you when you come back, Ahmed!

Enter fullscreen mode

Exit fullscreen mode

Now let’s write a new program that greets a user whose name has already been stored:

import json

filename = 'username.json'
with open(filename) as f:
    username = json.load(f)
    print(f"Welcome back, {username}!")

Enter fullscreen mode

Exit fullscreen mode

Welcome back, Ahmed!

Enter fullscreen mode

Exit fullscreen mode

We need to combine these two programs into one file. When someone runs remember_me. py, we want to retrieve their username from memory if possible; therefore, we’ll start with a try block that attempts to recover the username. If the file (username. json) doesn’t exist, we’ll have the except block prompt for a username and store it in (username. json) for next time:

import json

# Load the username, if it has been stored previously.
# Otherwise, prompt for the username and store it.
filename = 'username.json'
try:
    with open(filename) as f:
        username = json.load(f)
except FileNotFoundError:
    username = input("What is your name? ")
    with open(filename, 'w') as f:
        json.dump(username, f)
        print(f"We'll remember you when you come back, {username}!")
else:
    print(f"Welcome back, {username}!")

Enter fullscreen mode

Exit fullscreen mode

There’s no new code here; blocks of code from the last two examples are just combined into one file. Whichever block executes, the result is a username and an appropriate greeting. If this is the first time the program runs, this is the output:

What is you name? Ahmed
We'll remember you when you come back, Ahmed!

Enter fullscreen mode

Exit fullscreen mode

Otherwise:

Welcome back, Ahmed!

Enter fullscreen mode

Exit fullscreen mode

This is the output you see if the program was already run at least once.

Refactoring

Often, you’ll come to a point where your code will work, but you’ll recognize that you could improve the code by breaking it up into a series of functions that have specific jobs. This process is called refactoring. Refactoring makes your code cleaner, easier to understand, and easier to extend.

We can refactor remember_me. py by moving the bulk of its logic into one or more functions. The focus of remember_me. py is on greeting the user, so let’s move all of our existing code into a function called greet_user():

import json

def greet_user():
    """Greet the user by name."""
    filename = 'username.json'
    try:
        with open(filename) as f:
            username = json.load(f)
    except FileNotFoundError:
        username = input("What is your name? ")
        with open(filename, 'w') as f:
            json.dump(username, f)
            print(f"We'll remember you when you come back, {username}!")
    else:
        print(f"Welcome back, {username}!")

greet_user()

Enter fullscreen mode

Exit fullscreen mode

Because we’re using a function now, we update the comments with a docstring that reflects how the program currently works. This file is a little cleaner, but the function greet_user() is doing more than just greeting the user—it’s also retrieving a stored username if one exists and prompting for a new username if one doesn’t exist.

Let’s refactor greet_user() so it’s not doing so many different tasks. We’ll start by moving the code for retrieving a stored username to a separate function:

import json

def get_stored_username():
    """Get stored username if available."""
    filename = 'username.json'
    try:
        with open(filename) as f:
            username = json.load(f)
    except:
        return None
    else:
        return username

def greet_user():
    """Greet the user by name."""
    username = get_stored_username()
    if username:
        print(f"Welcome back, {username}!")
    else:
        username = input("What is your name? ")
        filename = 'username.json'
        with open(filename, 'w') as f:
            json.dump(username, f)
            print(f"We'll remember you when you come back, {username}!")

greet_user()

Enter fullscreen mode

Exit fullscreen mode

The new function get_stored_username() has a clear purpose, as stated in the docstring. This function retrieves a stored username and returns the username if it finds one. If the file (username. json) doesn’t exist, the function returns None. This is good practice: a function should either return the value you’re expecting, or it should return None. This allows us to perform a simple test with the return value of the function.

At if username: we print a welcome back message to the user if the attempt to retrieve a username was successful, and if it doesn’t, we prompt for a new username.

We should refactor one more block of code out of greet_user(). If the username doesn’t exist, we should move the code that prompts for a new username to a function dedicated to that purpose:

import json

def get_stored_username():
    """Get stored username if available."""
    filename = 'username.json'
    try:
        with open(filename) as f:
            username = json.load(f)
    except:
        return None
    else:
        return username

def get_new_username():
    """Prompt for a new username."""
    username = input("What is your name? ")
    filename = 'username.json'
    with open(filename, 'w') as f:
        json.dump(username, f)
    return username

def greet_user():
    """Greet the user by name."""
    username = get_stored_username()
    if username:
        print(f"Welcome back, {username}!")
    else:
        username = get_new_username()
        print(f"We'll remember you when you come back, {username}!")

greet_user()

Enter fullscreen mode

Exit fullscreen mode

Each function in this final version has a single, clear purpose. Each function in this final version of remember_me.py has a single, clear purpose. We call greet_user(), and that function prints an appropriate message: it either welcomes back an existing user or greets a new user. It does this by calling get_stored_username(), which is responsible only for retrieving a stored username if one exists. Finally, greet_user() calls get_new_username() if necessary, which is responsible only for getting a new username and storing it. This compartmentalization of work is an essential part of writing clear code that will be easy to maintain and extend.

Курс по Python: https://stepik.org/course/100707

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

FileNotFoundError

и программа
досрочно прерывается. Это неприятный момент, тем более, что программист наперед
не может знать, в каких условиях будет работать программа и вполне возможна
ситуация, когда ранее доступный файл становится недоступным, например, из-за
случайного его удаления, или изменении имени и т.п. То есть, при работе с
файлами нужно уметь обрабатывать исключение FileNotFoundError, чтобы
программа продолжала работать, даже если файл не будет найден.

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

try / except / finally

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

Формально,
операторы try / except / finally имеют,
следующий синтаксис (определение):

try:

       
блок операторов

       
критического кода

except
[исключение]:

       
блок операторов

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

finally:

       
блок операторов

       
всегда исполняемых

       
вне зависимости, от

        возникновения исключения

И в нашем случае
их можно записать, так:

try:
    file = open("my_file.txt", encoding='utf-8')
    s = file.readlines()
    print(s)
    file.close()
except FileNotFoundError:
    print("Невозможно открыть файл")

Смотрите, здесь
функция open() находится
внутри блока try, поэтому, если
возникнет исключение FileNotFoundError, то выполнение программы перейдет в блок
except и отобразится
строка «Невозможно открыть файл». Иначе, мы прочитаем все строки из файла,
отобразим их в консоли и закроем файл.

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

try:
    file = open("my_file.txt", encoding='utf-8')
 
    try:
        s = file.readline()
        print(s)
    finally:
        file.close()
        
except FileNotFoundError:
     print("Невозможно открыть файл")
except: 
    print("Ошибка при работе с файлом")

Мы здесь
прописываем еще один вложенный блок try, который будет учитывать все
возможные исключения и при их возникновении мы обязательно перейдем в блок finally для закрытия
файла. Это важная процедура – любой ранее открытый файл (функцией open()) следует
закрывать, даже при возникновении исключений. И вот такая конструкция try / finally нам гарантирует
его закрытие, что бы ни произошло в момент работы с ним. Но блок try / finally не отлавливает
исключения, поэтому они передаются внешнему блоку try и здесь мы
должны их обработать. Я сделал это через второй блок except, в котором не
указал никакого типа исключений. В результате, он будет реагировать на любые не
обработанные ошибки, то есть, в нашем случае – на любые ошибки, кроме FileNotFoundError.

Менеджер контекста для файлов

Более я не буду
углубляться в работу блоков try / except / finally. Приведенного
материала пока вполне достаточно, а в заключение этого занятия расскажу о
замене блока try / finally так называемым
файловым менеджером контекста, как это принято делать в программах на Python.

Так вот, в языке
Python существует
специальный оператор with, который, можно воспринимать как аналог
конструкции try / finally и в случае
работы с файлами записывается, следующим образом:

try:
    with open("my_file.txt", encoding='utf-8') as file:
        s = file.readlines()
        print( s )
 
except FileNotFoundError:
     print("Невозможно открыть файл")
except:
    print("Ошибка при работе с файлом")

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

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

try:
    with open("my_file.txt", encoding='utf-8') as file:
        s = file.readlines()
        print( s )
 
except FileNotFoundError:
     print("Невозможно открыть файл")
except:
    print("Ошибка при работе с файлом")
finally:
    print(file.closed)

После запуска
программы видим значение True, то есть, файл был закрыт. Даже если
произойдет критическая ошибка, например, вызовем функцию int() для строки s:

то, снова видим
значение True – файл был
закрыт. Вот в этом удобство использования менеджера контекста при работе с
файлами.

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

Курс по Python: https://stepik.org/course/100707

Видео по теме

In this article we will discuss error handling using Python With Statements Try/Except/Finally statements, show how to use these in combination, and compare how it works to try/catch code blocks in other languages.

What is error handling?

Error handling is when you put in some extra code to tell your script what to do when things don’t go totally to plan. perhaps you try to open a file that isn’t there. Or perhaps a user puts in unexpected input.

Without any error handling, your program or script will simply crash, throw an error, and quit running. It is important to at least put in a minimal amount of error handling to ensure that your script/program will run reliably.

Try/Catch Statements

In many languages, you use Try/Catch statements for error handling. In C# for example, you might write some code that looks like this:

Try{
string text = System.IO.File.ReadAllText(@"C:\Users\Public\TestFolder\WriteText.txt");

}
Catch(exception e){
console.writeline(e);
}

The above code will attempt to read the txt file. If it cannot read the file, it will throw an exception. The catch code block then catches that exception into variable e. And uses the console.writeline method to print the exception onto the console. That way you can see what happened.

If you didn’t put in the try/catch blocks, the exception would have still been shown on the screen, but the application would have crashed and you have had to re-launch it. Sometimes on your computer, you might get an error about an un-caught exception right before your program closes. Those are cases like this one where the system.io.file.readalltext method was not in a try block of code.

Python Try, Exception & Finally Statements

Python does not have try/catch blocks. Instead, python has try/exception blocks. They really do the same thing but have different names.

Let’s look at an example written in python:

try:
  f = open(“test.txt", 'r')
  data = f.read()
  print("Trying")
except:
  print("Fiddlesticks! Failed")
finally:
  print("Finally!")
  print("All Done")

In the above script, we start the try statement and we attempt to execute the code in that section. If it succeeds, it will print the text trying. It will then jump down to the finally section and print Finally! followed by “All Done” on the console.

If the above script is not able to open the test.txt file, it will throw an exception and print “FIddleSticks!” to the console, followed by Finally.

The next logical question about these statements are what are the roles of each section?

  • The Try code block is the code you really want to execute.
  • The exception code block is the bit of code that you want to execute in the event you get an error while executing the code in the try code block.
  • The Finally code block is code that you want to execute regardless of the outcome.

More helpful errors

You may find that simply writing fiddlesticks when you have an error is not all that helpful. You need to capture the error so you can write it to a log file, or maybe you want to display the error on the screen.

Lets try executing this code in our python script:

f = open(“test.txt", 'r')
data = f.read()
print(data)

The above script will read test.txt and print the contents to the console. Unfortunately, if test.txt does not exist, you will get an error like this one:IOError: [Errno 2] No such file or directory: ‘test.txt’

Notice the type of error is IOError. That is helpful because we can create an exception block of code specifically around IOErrors. Lets look at how we would write that code:

try:
  f = open(“test.txt", 'r')
  data = f.read()
  print(data)
Except IOError as e:
  Print(e)
except:
  print("Fiddlesticks! Failed")
finally:
  print("Finally!")
print("All Done")

The above code will attempt to run what is in the try block. If it failed with an IOError, it will run the code in the except block. In this case, it will print out the error saying something about how it could not open or close a file. It will then run the finally code block when everything is finished.

If we want to have different logic for different kinds of exceptions, we could keep adding similar code like the code below.  Notice we call out each type of exception. Then we have the option to have a different remediation step for each exception type.

try:
  f = open("test.txt", 'r')
    Try:
      data = f.read()
      print(data)
    except IOError as e:
      print(e)
    except ValueError as e:
      print(e)
    except EOFError as e:
      print(e)
    Except:
      print(“unknown error”)
    Finally:
      f.close()
except:
  print("Fiddlesticks! Failed")
finally:
  print("All Done")

In the case of our example above, we are doing the exact same logic for each exception, so we might as well consolidate the exception handling into a single exception line.  That would look like this:

try:
  f = open("test.txt", 'r')
  data = f.read()
  print(data)
except (IOError, ValueError, EOFError) as e:
  print(e)
except:
  print("Fiddlesticks! Failed")
finally:
  print("All Done")

In the above example, we will print out the exception if it matches IOError, Valueerror, or EOFError. If it does not match any of those, it will print out Fiddlesticks. Here are a few of the most common exceptions you may want to handle:

  • IOError – file cannot be opened
  • ImportError – cannot find the specified module
  • EOFError – When the input reaches the end of a file and no more data can be read
  • ValueError – function receives an argument that has the right type but an invalid value
  • KeyboardInterrupt – User hits the interrupt key (Ctrl+D or Ctrl+C)

Or if you want a more comprehensive list of Python Exceptions You check look here.

Creating Custom Exceptions

In the previous section, we. were focused on handling exceptions using the exceptions that are built-in to Python. However, as you are developing your application, you will most likely encounter situations where you want to handle exceptions a bit differently. This is when you would create your own custom exceptions.

To handle your own custom Exceptions, you have to create a class for each exception type. Then put in some code for what to do when that exception has occurred. The most basic of exception classes looks like this:

class MyError(Exception):
    pass

raise MyError("Test Exception!")

**Note the pass statement is there just for syntax reasons. You could put additional code there instead.

If you run the above code, you will see output that says Test Exception, like this:

Now that we know the basics to create our own custom exception. Let’s start with a new example. We have a basic math function that just adds numbers together and returns the sum:

def addnumbers(x,y):
    return x+y

print(addnumbers(3,2))

When we run the code above, the output is the number 5. Now let’s say that we want to throw an exception if someone passes in the number 3. We can create a custom exception that lets the user of our function know that we don’t allow the number 3 as an input. Then we can add some code to check if the number 3 was passed in, and raise an exception in that case.

class BadNumbersError(Exception):
    pass

def addnumbers(x,y):
    if x ==3:
        raise BadNumbersError("We don't like the number 3")
    return x+y

print(addnumbers(3,2))

In the code above, you can see we created a class called BadNumbrersError. Then in our function, we added a check. If x==3, then raise a BadNumbersError exception and pass in the text “We don’t like the number 3”.

Next, we call our function and pass in values of 3 and 2. WIthout this new exception, the output would be the number 5. But now that we have added in our exception when you run the code, you should see what our new custom exception looks like.

As you can see, we trigger our exception and present a message to the user that says that we don’t like the number 3 when calling this function.

Python With statements

With statements can be used with try/catch statements to reduce the amount of code you need to write for handling different kinds of errors.

With statements call the __Enter__ and __Exit__ functions that are part of a given class. An example of this is with the File open class.

To properly handle errors when opening files, you would need some code similar to this:

try:
  f = open(“test.txt", 'r')
    Try:
      data = f.read()
      print(data)
    except IOError as e:
      print(e)
    Except:
      print(“unknown error”)
    Finally:
      f.close()
except:
  print("Fiddlesticks! Failed")
finally:
  print("All Done")

Before we get into the with statements, lets examine this code a bit. In the previous sections, we wrote some code to catch exceptions when opening a file. But the other issue is what happens if we have an issue after the file is already open. We need to make sure we close the file. We could put that in the finally section at the very bottom. But that will throw an exception if the original file never successfully opened. The result is this big mess of nested try/except statements to hopefully catch all of the different scenarios you may encounter.

Lucky for us, the makers of Python came out with a With Statement. Like I said previously, a with statement has an __enter__ and an __exit__ function that it calls at the beginning and the end of the statement. This allows some of that code to be removed from the big try/except mess I demonstrated above. An example of a with statement can be seen below:

with open(“test.txt”,r) as f:
text=f.read()
Print(text)

The above text will still throw an exception if test.txt does not exist. However, we no longer have to remember to call f.close when we are all finished with the file. If we do have the file open and an error occurs, the with statement will handle closing out the file handles for me. What this means is we can use the with statement in conjunction with our try/except code blocks to give us cleaner looking code. Let’s look at another example:

try:
  with open(“test.txt", 'r’) as f:
    data = f.read()
    print(data)
Except IOError as e:
  Print(e)
except:
  print("Fiddlesticks! Failed")
finally:
  print("Finally!")
print("All Done")

Notice the above example looks a lot like our original example. It is the same number of lines, and it is still very readable. However, it gives us similar functionality to the second example with the nested try/except loops.

Summary

In today’s article we discussed What is Error handling, What is the role of Try/Catch code blocks. How to set up exceptions, how to create our own custom extensions, and what a with statement does for us.

Error handling is a very important part of writing good software. Software without proper error handling will be unstable, and may not give good output when invalid input is entered into it.

Понравилась статья? Поделить с друзьями:
  • Witcher3 exe системная ошибка отсутствует vcomp110 dll
  • Word выдает ошибку при открытии файла
  • Word выдает ошибку 0xc0000142
  • Witcher3 exe системная ошибка отсутствует msvcr120 dll
  • Word 2013 ошибка при направлении команды приложению