Блог

Исключения в Python. Конструкция try-except-finally

Это продолжение статьи Исключения в Python. Введение

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

Для тестирования этого кода понадобится добавить в текущий каталог программы текстовой файл tmp.txt следующего содержания:

5
7

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

try:
    f = open('tmp.txt', 'r+')
    output = ''
    for line in f:
        num = int(line)
        result = 10 / num
        output += f'{result}\n'

    f.write(output)

except Exception as e:
    print(e.__class__, e)

finally:
    print('f.closed = ', f.closed)
    f.close()
    print('f.closed = ', f.closed)

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

>>>
    f.closed =  False
    f.closed =  True

А текстовой файл tmp.txt увеличится на две новых строки:

>>>
    5
    7
    2.0
    1.4285714285714286

Эти добавленные строки - дробные числа в строковом формате. Поэтому, если мы ещё раз запустим скрипт, то на этот раз получим ошибку и сообщение:

>>>
    <class 'ValueError'> invalid literal for int() with base 10: '2.0\n'
    f.closed =  False
    f.closed =  True

Однако, как мы видим, не смотря наличие или отсутствие ошибок исполнения кода, и в первом и во втором случае был выполнен блок finally: файл был закрыт с выводом соответствующего сообщения: f.closed = True.

Продолжить в статье Исключения в Python. Перехват специфических ошибок

Вернуться в начало, на статью Исключения в Python. Введение

Читать дальше >>

Исключения в Python. Конструкция try-except-else

Это продолжение статьи Исключения в Python. Введение

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

Для подобных случаев существует конструкция try-except-else, когда ветвление программы зависит от наличия или отсутствия ошибки в блоке try. Если ошибка найдена, то программа выполняет блок except. Если нет — выполняется блок else:

while True:
    line = input('Please enter an integer: ')
    if line == 'end':
        break

    try:
        result = 100 / int(line)
        print(result)

    except Exception as e:
        print(e.__class__, e)
        print('Please enter another number! ')

    else:
        print('Successful input!')
        break

Продолжить в статье Исключения в Python. Конструкция try-except-finally

Вернуться в начало, на статью Исключения в Python. Введение

Читать дальше >>

Исключения в Python. Конструкция try-except

Это продолжение статьи Исключения в Python. Введение

Перехватить все возможные исключения (Exceptions) в этой ситуации мы сможем с помощью конструкции try-except:

while True:
    line = input('Please enter an integer: ')
    if line == 'end':
        break

    try:
        result = 100 / int(line)
        print(result)

    except Exception as e:
        print(e.__class__, e)
        print('Please enter another number! ')

Как видим, код не стал больше, скорее даже наоборот — он стал понятнее.

Однако помимо этого мы получили возможность перехватывать абсолютно все ошибки, которые могут возникнуть в блоке try при вычислении переменной result:

  1.) Ошибка деления на ноль:

>>>
    Please enter an integer: 0
    <class 'ZeroDivisionError'> division by zero
    Please enter another number!

  2.) Ошибка ввода дробного значения:

>>>
    Please enter an integer: 1.1
    <class 'ValueError'> invalid literal for int() with base 10: '1.1'
    Please enter another number!

  3.) Ошибка ввода нечисловых символов:

>>>
    Please enter an integer: asd
    <class 'ValueError'> invalid literal for int() with base 10: 'asd'
    Please enter another number!

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

Продолжить в статье Исключения в Python. Конструкция try-except-else

Вернуться в начало, на статью Исключения в Python. Введение

Читать дальше >>

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

Python Exception Handling. Introduction

Не ошибается только тот, кто ничего не делает. Поэтому ошибки в коде — естественны и неизбежны.

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

В первом случае это Syntax Errors (синтаксические ошибки), а во втором — Exceptions (исключения).

Синтаксическая ошибка — это ошибка в синтаксисе последовательности символов или токенов. Иными словами, синтаксическая ошибка — это несоответствие правилам записи кода, без исправления которой код не может быть запущен.

Исключения возникают, если программа синтаксически верна, но в некоторых случаях (при некоторых значениях переменных) исполнение кода приводит к ошибке. То есть исключения возникают в тех случаях, когда программист не учёл или не предусмотрел какое-либо событие, которое в итоге привело в ошибке и остановке программы.

Классический пример — ошибка деления на 0:

>>> 5 / 0
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    ZeroDivisionError: division by zero

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

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

  1. Место возникновения ошибки (строка #1 файла “стандартный ввод” File "", line 1, in <module>);
  2. Класс ошибки (деление на ноль — ZeroDivisionError);
  3. И даже подробную характеристику этого класса (division by zero)

Всё это так — “сервис исключений” (если его можно так назвать) в Python, позволяет эффективно бороться с ошибками после остановки программы. А может ли он помочь ещё до остановки программы, в процессе её выполнения?

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

while True
    line = input('Please enter an integer: ')
    if line == 'end':
        break

    if line == '0':
        print('Zero gives an error! Please enter a different number!')
           continue

    result = 100 / int(line)
    print(result)

Программа будет работать до тех пор, пока пользователь не введёт end.

В этом примере учтена возможность ввода пользователем нулевого значения. Для этого каждое введённое значение прежде сравнивается с нулём. И если пользователь ввёл 0, то программа его не примет и попросит ввести другое значение.

Это действительно решает проблему. Но что будет, если число окажется дробным? А если пользователь по ошибке введёт букву и другой символ? В этом случае всё равно будет аварийное завершение программы. Мы конечно же может предусмотреть и эти два варианта ошибочного ввода и поставить точно такие же “заглушки” как и в случае ввода число 0. Но где гарантия, что не может возникнуть других ошибок ввода помимо этих трёх?

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

Исключения в Python. Конструкция try-except

Конструкция try-except — далеко не единственный способ использования исключений в Python. Более подробно этот вопрос рассмотриваются в следующих статьях:

Исключения в Python. Конструкция try-except-else

Исключения в Python. Конструкция try-except-finally

Исключения в Python. Перехват специфических ошибок

Исключения в Python. Операторы вызова исключений raise и assert

Читать дальше >>

Регулярные Выражения. Regex Lookaround (Проверка окружения шаблона)

Более подробно эта тема рассматривается в курсе Python Fundamentals

В теме анкоров (или якорей) была рассмотрена возможность использования паттерна (шаблона) относительно начала или конца строки, или начала или конца слова.

С помощью regex lookaround, тоже самое можно сделать в любой части строки или слова. Причём в конечном итоге, и что ОЧЕНЬ важно, символы на которые ссылается lookaround НЕ БУДУТ добавлены в итоговый результат.

Анализ символов может быть как непосредственно перед шаблоном - метод lookahead(смотреть вперёд), так и за шаблоном lookbehind (посмотреть назад).

Кстати, в русскоязычной печати и блогосфере lookahead иногда называется “опережающая проверка” и lookbehind - “ретроспективная проверка”. Однако пользоваться аутентичными английскими терминами lookahead и lookbehind в данном случае будет более логично, и, что самое главное, более понятно.

Итак в чём же здесь суть? Попробуем разобрать это на примере.

Задача:
Допустим, нам необходимо найти имена, которые начинаются с _ и выбрать эти имена, отбросив нижнее подчёркивание.

Пример:
_id name list _name

На первый взгляд, самым простым решением будет: _\w+
Но ведь в задаче сказано, что имена нужны без подчёркиваний!
Другим относительно приемлемым решением будет выделить имя после нижнего подчёркивания в группу: _(\w+) И в Match information мы видим, что хотя и match не совпало с нужным нам результатом, но появились дополнительные группы с правильным ответом.

Однако, для того, чтобы получить правильный ответ в match, нам надо использовать lookaround. И, в частности, в этом конкретном случае - lookbehind (взгляд назад, поскольку поисковая машина проверяет символы слева-направо, поэтому переход в начало слова будет с точки зрения поискового алгоритма возвратом назад).

Для этого условие начала нижнего подчёркивания мы перенесём в конструкцию lookbehind:
(?<=_)\w+

Как видим, теперь нижнее подчёркивание вообще пропало из выделения, но зато в match появилось именно то, что нам нужно!

Это был пример positive lookbehind, когда наличие указанного элемента (или группы элементов) - истина.

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

В этом случае знак = мы меняем на !:
(?<!_)\w+

Как видим, это не дало ожидаемого результата: все слова выделились и стали валидными. Это произошло из-за того, что знак нижнего подчёркивания входит в \w, поэтому, для выполнения условия, выделение сместилось на знак влево. Изменим \w на [a-z]:
(?<!_)[a-z]+

Теперь выделение сместилось на символ вправо!
Чтобы исправить ситуацию добавим знак границы слова \b:
(?<:!_)\b[a-z]+

Теперь, благодаря negative lookbehind, всё работает как надо!

Аналогичную проверку можно выполнить для знака нижнего подчёркивания в КОНЦЕ слова. Для этого сначала изменим исходный текст:

Изменённый пример к предыдущей задаче:
id_ name list name_

Сначала попробуем выделить все слова: [a-z]+

Далее, с помощью positive lookahead (позитивного взгляда вперёд или более благозвучной, но менее понятной “позитивной упреждающей проверки”) оставим только те, что заканчиваются на нижнее подчёркивание:
[a-z]+(?=_)

Теперь, c помощью negative lookahead (негативный взгляд вперёд), попробуем наоборот, оставить только слова БЕЗ нижнего подчёркивания. И для этого знак (=) поменяем на (!).

Как видим, это помогло лишь отчасти: слова с подчёркиванием тоже пока выделены, правда, в них теперь выделен на один символ меньше. Чтобы их совсем убрать, жёстко ограничим конец слов границей \b:
[a-z]+\b(?!_)

Ура, получилось!

И ещё раз выведем выведем вместе все четыре рассмотренных варианта lookaround:

Pattern Type of Lookaround
(?<=_)\w+ positive lookbehind
(?<!_)\w+ negative lookbehind
[a-z]+(?=_) positive lookahead
[a-z]+(?!_) negative lookahead

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Lazy and Greedy Search (Ленивый и жадный поиск)

Более подробно эта тема рассматривается в курсе Python Fundamentals

Если мы что-то ищем в строке с помощью регулярного выражения, то по-умолчанию производится широкий или “жадный” (greedy) поиск.

Рассмотрим пример. Допустим, мы хотим получить в тексте слова, ограниченные двойными кавычками:

Without "economic freedom", there can be no other "freedom".
Margaret Thatcher.

По идее, нам нужен паттерн, который начинается с первой кавычки, далее показывает любой символ (точка) и далее идёт квантификатор (плюс), который вмещает в себя один или более символов. И заканчивается всё это снова кавычками:

r ='\".+\"'

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

"economic freedom", there can be no other "freedom"

Почему? Потому что так работает алгоритм поиска совпадения в регулярном выражении.

Если не указано иное (то есть по умолчанию) алгоритм поиска работает от первого совпадения до конца текста. А затем, идёт посимвольно назад, до выполнения первого совпадения по условию поиска.

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

"economic freedom", "freedom"

Для этого после квантификатора + необходимо поставить знак вопроса ?:

r ='\".+?\"'

Это относится не только к знаку “плюс”, но и к другим квантификаторам:

Symbols Their meaning:
+ 1 or > characters
* 0 or > characters
{min, } nmin or > times
{,max} from 0 to nmax times
{min, max} from nmin to nmax times

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Group (Группа или Группировка)

Более подробно эта тема рассматривается в курсе Python Fundamentals

Часть паттерна, окружённая круглыми скобками считается Группой.

Рассмотрим пример. Из текста необходимо извлечь телефонные номера. Важное ограничение: валидными считаются номер в формате 000 00 00 c блоками цифр, разделёнными одновременно либо двумя пробелами, либо двумя дефисами, либо двумя бэкслешами. Смешанное разделение в любой комбинации считается невалидным. Пример текста:

111 22 33-aad 232-15-11 ff 15 15 555-22-78 223/99-37 223/25/987 123-35 26 258-85-45

Исходя из того, что мы уже знаем, единственным вариантом решением будет объединение всех трёх вариантов с помощью ИЛИ (|):

\d{3}-\d{2}-\d{2}|\d{3} \d{2} \d{2}|\d{3}\/\d{2}\/\d{2}

Однако, если мы выделим разделители в отдельную группу (-| |\/) в начале шаблона, то в случае, если эта группа встречается в шаблоне несколько раз, мы можем не вставлять в это место всю группу целиком, а всего лишь сослаться на неё с помощью бэкслеш+порядковый номер группы (в нашем случае это будет \1, поскольку в шаблоне присутствует только одна группа):

\d{3}(-| |\/)\d{2}\1\d{2}

Важно ещё раз отметить, что ссылка на группу \1 означает, допустимость только того символа, что встретился в группе. Иными словами, если в самой группе совпал дефис, то в ссылке на группу \1 допустим ТОЛЬКО дефис и больше никакой другой символ. Аналогично, если в группе совпал пробел, то пробел должен быть и в ссылке на группу. То же самое и для бэкслеша.

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Metacharacters (Метасимволы)

Более подробно эта тема рассматривается в курсе Python Fundamentals

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

Symbols Their meaning:
. (dot) Any character except \n
\d Any digit
\w Any word character: [A-Za-z0-9_]
\s Any whitespace characters: [ \n\r\t\f]
\D Any NON-digit
\W Any NON-word character: [A-Za-z0-9_]
\S Any NON-whitespace characters: [ \n\r\t\f]

  • . (dot) - точка в регулярных выражениях означает совсем не точку, а вообще любой символ (чтобы ввести именно точку, необходимо перед самой точкой поставить символ экранирования бэклсеш, то есть сделать вот так: \.) ;
  • \d - означает любой цифровой символ (аналог [0-9]) ;
  • \w - любой символ слова, т.е. большие и маленькие буквы, цифры и знак подчёркивания [A-Za-z0-9_] ;
  • \s - любой пробельный символ (перевод строки, знак табуляции и т.д.) [ \n\r\t\f] ;
  • \D - большие буквы означают инверсию. Поэтому, если \d означает любой ЦИФРОВОЙ символ, то \D будет означать любой НЕ цифровой символ ;
  • \W - любой НЕ символ слова (т.е. все символы, кроме [A-Za-z0-9_] ;
  • \S - любой НЕ пробельный символ ;

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Anchors (Якоря)

Более подробно эта тема рассматривается в курсе Python Fundamentals

Бывают ситуации, когда нам надо не просто найти совпадение символов в тексте, но также быть уверенными в том, что эта найденная комбинация символов выполняет определённые условия относительно другой части текста. Например, при валидации пароля, важно не просто получить совпадение символов пароля My_Pa$$worD, но также быть уверенными в том, что пароль полностью занимает всю строку от начала и до конца.

Например, все эти три строки содержат комбинацию символов, удовлетворяющих паттерну My_Pa$$worD:

   My_Pa$$worD___
My_Pa$$worD
aaa_asdf_ My_Pa$$worDllfff

Однако единственно правильным вариантом будет только пароль во второй строке.

Для выполнение этого условия в начало и конец паттерна пароля добавить символы (якоря) начала ^ и конца строки $: ^My_Pa\$\$worD$

Таблица анкоров: <\p>

Symbols Their meaning:
^ Start of line
$ End of line
\b Boundary of word (start or end of word)
\B Inverse of \b (non-start or non-end of word)

  • ^ - символ caret, означает начало строки ;
  • $ - символ конца строки ;
  • \b - символ границы слова (начало или конец слова) ;
  • \B - символ инверсии границы слова (только НЕ начало или НЕ конец слова) .

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные выражения. Character class (Класс символов)

Более подробно эта тема рассматривается в курсе Python Fundamentals

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

Значительно чаще требуется более сложный поиск, например поиск текста, удовлетворяющего определённым критериям. Например, поиск телефонных номеров в формате 000-00-00.

Очевидно, что в начале нашего паттерна, соответствующего поиску указанного формата телефонного номера, потребуется указать комбинацию чисел от 0 до 9, повторяющуюся ровно три раза. Ни больше и не меньше.

В том случае, когда на месте указанного символа может быть любой символ из выбранного диапазона, удобнее всего использовать Character class (Класс символов).

Например, если нам нужна любая буква из набора a, b, c или d, то класс символов мы запишем как [abcd]. И поскольку указанные символы расположены в таблице кодировки по соседству, то запись [abcd] можно сократить до вида [a-d].

Пример класса символов [0-9] - означает, что нам подойдёт любой символ от 0 до 9. И если мы теперь добавим {3}, то это будет означать, что нам нужна группа из трёх чисел подряд: [0-9]{3}.

Далее добавляем дефис и ещё 2 последовательности по два символа. И в результате получаем итоговый паттерн, удовлетворяющий заданному условию:
[0-9]{3}-[0-9]{2}-[0-9]{2}

Кстати, забегая вперёд, следует отметить, что есть один способ записать тоже самое:
\d{3}-\d{2}-\d{2}
В этом случае, для записи последовательности чисел используется специальный символ \d (бэкслеш-ди), называемый
Metacharacter (Метасимвол) .

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Список тэгов