Guard block в python что это
Перейти к содержимому

Guard block в python что это

  • автор:

Guard block в python что это

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

Здесь первый шаблон

Соответствует любому объекту Person, у которого атрибут age меньше 18. Собственно часть if age < 18 и представляет ограничение. Соответственно, если у пользователя возраст меньше 18, то будет выводьтся одно сообщение, если больше 18, то другое.

Подобным образом можно вводить дополнительные ограничения:

Условия ограничений могут быть более сложными и составными по структуре:

В данном случае функция получает кортеж data. Оба шаблона в конструкции match соответствуют двухэлементному кортежу. Но первый шаблон также применяет ограничение name == «admin» or age not in range(1, 101) , в соответствии с которым первый элемент кортежа должен иметь значение «admin», а второй должен находиться вне диапазона 1-101.

Guard clauses for better “if” statements — Python

Readability needs to be key when writing complex functions

Ícaro

Lemon Code

Photo by Nery, snippet from @carbon_app

Lots of things can make code hard to understand. Confusing variables, high coupling, unnecessary complexity, and many other that we see (and sometimes do) everyday.

Sometimes we can’t avoid functions that need lots of validations to make sure it can execute what it’s there for. You can see an example below:

That’s very simple but you can see that the nested if statements make a “stairs” effect in the code, increasing line width which can harm code readability and increase complexity (by having you need to “remember” all the previous conditions of each if ).

The Guard Clause pattern helps in these cases by “inverting” that way of thinking and rapidly returning false conditions and keeping the important code blocks (usually the longest) in the lowest indentation level possible.

We have increased the line count but it’s a valid trade-off for the code quality improvements that counter what we mentioned in the previous example.

Below is another example of before and after applying guard clauses:

All the source code shown and explained here (mixins, functions, examples, samples, etc.) is available in the GitHub repository below along with other helpful projects I’ve developed and wrote about. Feel free to check it out, use them in your own work and help me improve them with your feedback.

Сопоставляем с образцом как Pythonista

Одним из самых нашумевших нововведений Python 3.10 стало так называемое структурное сопоставление с образцом (structural pattern matching). Этот мощный инструмент берёт своё начало в функциональных языках программирования, а в последнее время постепенно появляется и во многих мейнстримовых языках (Java, C#, Kotlin, Swift, и т.д.). Как всегда, Python старается не отставать и идти в ногу со временем. Так зачем же популярные языки программирования добавляют поддержку этого механизма? В чём его отличие от простого условного оператора if ? И вообще, в чём практическая польза сопоставления с образцом? Пробуем разобраться далее.

О чём эта статья

Все примеры кода приведены для версии Python 3.10

Предыстория

В Python существует возможность распаковки последовательностей (sequence unpacking), которая позволяет одновременно присваивать нескольким переменным значения элементов последовательности:

Оба варианта приводят к одному и тому же результату, но, думаю, можно согласиться, что использование распаковки повышает выразительность и сокращает количество строк кода. Такой механизм работает, если количество указанных переменных совпадает с количеством элементов в последовательности. Здесь важно отметить, что, хотя мы и используем квадратные скобки (как при обычном объявлении списков) в левой части выражения распаковки, новый список [one, two, three] не создаётся. Эта особенность нам пригодится дальше для понимания работы конструкций match и case .

Подобным образом можно распаковывать не только списки, но и кортежи, а множественное присваивание — это фактически использование механизма распаковки кортежа:

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

Элементы списка, которые нас не интересуют в данном случае, будут присвоены переменной _ . Символ нижнего подчёркивания обычно используется для обозначения того факта, что нам неважно конкретное значение переменной.

Переходим к сопоставлению

Сопоставление с образцом в некотором смысле является расширением идеи распаковки последовательностей. В одном из примеров выше в роли образца выступает выражение [one, two, three] до оператора = .

С этим образцом и сопоставляется (другими словами, сравнивается) значение переменной numbers . Если это сопоставление успешно, то значения соответствующих элементов списка присваиваются указанным переменным. Если же структура (в случае списка — это количество элементов) переменной numbers будет отличаться от образца, то сопоставление будет считаться неудачным, и возникнет исключение ValueError :

Match и case

Замечательно, но что, если нас не устраивает исключение ValueError в случае неудачного сопоставления, и мы хотим как-то заранее проверить, что структура списка numbers соответствует нашему образцу? Одним из вариантов может быть явная проверка условий в if и elif :

Новые ключевые слова match и case делают такого рода проверки более выразительными:

После ключевого слова match указывается выражение для сопоставления, в данном случае — переменная numbers . В каждом блоке case находится образец, с которым мы хотим сопоставить значение переменной numbers . Также как и проверка условий в if и elif , сопоставление с образцом в каждом блоке case может быть либо успешным, либо неудачным. В случае успешного сопоставления переменным указанным в образце ( one , two , three ) присваиваются значения соответствующих элементов списка numbers , ещё говорят, что переменные связываются со значениями.

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

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

Сопоставление с таким образцом будет успешным, если список numbers состоит из трёх элементов, а также первый и последний элемент равны 1 . В случае успешного сопоставления, переменная number связывается со значением второго элемента списка.

Сопоставление с литералами

В блоках case выше мы рассматривали только образцы последовательностей (sequence patterns) для сопоставления. Конечно, в качестве образцов могут использоваться и другие выражения. Одним из самых простых для понимания видов образцов являются литералы, например, числа и строки. Сопоставление с такими образцами фактически эквивалентно простому сравнению с помощью оператора == .

Литералы True , False и None также могут использоваться как образцы. Такое сопоставление будет эквивалентно использованию оператора is :

Аналогичный пример выше с использованием блока if выглядел бы следующим образом:

Захват значений в образце

Об образцах в блоках case следует думать как об особых конструкциях языка. Исполнение кода в образце после ключевого слова case может отличаться от исполнения точно такого же кода в другом месте программы вне блока case . Одним из примеров этого различия являются так называемые захватывающие образцы (capture patterns). Рассмотрим следующий пример:

Возможно, кого-то удивит, что значение переменной color соответствует образцу red в данном случае, ведь значение переменной red — это строка «red» . Но в блоке case действуют свои правила! Здесь red — это не переменная со значением «red» , а образец, сопоставление с которым всегда проходит успешно, именно поэтому подобные образцы называются неопровержимыми (irrefutable).

Образец red как бы захватывает значение color целиком, и после успешного сопоставления переменная red связывается со значением «yellow» . Конечно, вместо red мы могли бы использовать более подходящее имя, чтобы не вносить такую путаницу, но имя red особенно наглядно показывает этот не совсем интуитивный момент.

Wildcard

Помните, как мы использовали нижнее подчёркивание _ при распаковке последовательностей для обозначения элементов, которые нас не интересуют?

Для подобных целей существует особый образец _ , который называется wildcard и является специальным видом захватывающего образца (capture pattern). Образец _ также является неопровержимым, иначе говоря, любое значение соответствует этому образцу, и обычно используется для обработки сценариев по умолчанию, наподобие ветки else в условном операторе if . Блок case с образцом _ обязательно должен быть последним в блоке match :

Сопоставление с именованными значениями

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

Здесь работает следующее правило: если в образце есть . , то происходит сопоставление со значением соответствующего атрибута, если же . в образце нет, то используется захват значения:

Этот же механизм позволяет использовать перечисления (enum) в качестве образцов:

Словари

Словари также можно проверять на соответствие специальным образцам:

В примере выше словарь numbers соответствует образцу, и переменная two связывается со значением в словаре по ключу «two» . Как видно из примера, особенностью сопоставления словарей является то, что дополнительные ключи, которые не указаны в образце, не учитываются при сопоставлении. В качестве ключей в образце могут использоваться либо литералы, либо образцы именованных значений (см. предыдущий раздел).

Охранные выражения

Охранные выражения (guards) позволяют задать дополнительные условия в блоке case , которые нельзя указать в образце. Посмотрим на следующий пример:

Список numbers успешно сопоставится с образцом, только если условие в охранном выражении будет истинно. В случае, если условие в охранном выражении вернёт False , то сопоставление будет считаться неудачным.

Группа образцов

В блоках case можно сопоставлять значения с несколькими образцами, используя разделитель | , что означает логическое ИЛИ. Например, следующий код сопоставляет значение number в трёх блоках case :

Образец в первом блоке case имеет опцию ИЛИ, т.е. если значение number соответствует любому из значений в этом образце, то сопоставление будет успешным, и выполнится код блока case .

Классы как образцы

Одной из самых часто используемых функций в Python является проверка isinstance . Эта функция позволяет проверить класс объекта, чтобы убедиться, что с объектом можно выполнить определённые действия:

Использование сопоставления с образцом позволяет проверять не только класс, но и внутреннюю структуру объекта:

В примере выше сопоставление будет успешным только в том случае, если shape — это объект класса Rectangle и ширина этого прямоугольника равна 2.5.

Здесь следует ещё раз обратить внимание, что код в образце никогда не создаёт новых объектов (аналогично распаковка списка не создаёт нового списка). Выражение Rectangle(length, width=2.5) в блоке case — это образец, а не создание объекта Rectangle .

Mypy и проверка на полноту

Далее в примерах я буду использовать аннотации типов и инструмент для статической проверки типов Mypy версии 0.961 с настройками по умолчанию. Аннотации типов в Python необязательны, но их использование и проверка Mypy позволяет значительно повысить надёжность кода.

Добавим в пример из предыдущего раздела несколько новых геометрических фигур:

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

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

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

Внимательный читатель заметит, что в этот раз мы забыли указать образец для класса Square , что с большой вероятностью приведёт к ошибкам в работе нашей программы. Однако если запустить Mypy, то мы сразу увидим соответствующую ошибку:

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

Если мы расширим наш тип Shape и добавим новую фигуру Triangle :

то при проверке Mypy сообщит нам обо всех местах в коде, где нужно добавить образец для новой фигуры во все блоки match :

В нашем случае мы должны расширить блоки match в двух функциях для вычисления площади и периметра треугольника. Если представить, что наша программа гораздо больше, и таких функций не две, а несколько десятков, то ценность проверки на полноту (exhaustiveness checking) значительно возрастает.

Итоги

Сопоставление с образцом является очень мощным средством языка. Этот механизм позволяет выполнять сложные проверки структуры объектов, что даёт возможность сделать код программы более выразительным и легкочитаемым. В свою очередь использование Mypy для статической проверки блоков match и case повышает надёжность кода, а проверка на полноту (exhaustiveness checking) является очень важным свойством для гарантий корректности и рефакторинга программ.

Compound statements

Составные операторы содержат (группы)других операторов;они каким-то образом влияют или контролируют выполнение этих других операторов.Как правило,составные утверждения занимают несколько строк,хотя в простых воплощениях все составное утверждение может содержаться в одной строке.

Операторы if , while и for реализуют традиционные конструкции потока управления. try указывает обработчики исключений и / или код очистки для группы операторов, в то время как оператор with позволяет выполнять код инициализации и завершения вокруг блока кода. Определения функций и классов также являются синтаксически составными операторами.

Составной оператор состоит из одного или нескольких «предложений». Предложение состоит из заголовка и «набора». Заголовки предложений конкретного составного оператора находятся на одном уровне отступа. Заголовок каждого предложения начинается с уникального ключевого слова и заканчивается двоеточием. Набор — это группа операторов, управляемая предложением. Набор может быть одним или несколькими простыми операторами, разделенными точкой с запятой, в той же строке, что и заголовок, после двоеточия заголовка, или это может быть один или несколько операторов с отступом в последующих строках. Только последняя форма набора может содержать вложенные составные операторы; следующее является незаконным, в основном потому, что было бы неясно, к какому предложению if будет принадлежать следующее предложение else :

Также обратите внимание, что в этом контексте точка с запятой привязывается сильнее, чем двоеточие, поэтому в следующем примере выполняются все или ни один из вызовов print()

Обратите внимание, что операторы всегда заканчиваются символом NEWLINE , за которым может следовать DEDENT . Также обратите внимание, что необязательные предложения продолжения всегда начинаются с ключевого слова, которое не может начинать оператор, поэтому двусмысленности нет (проблема «висячего else » решается в Python, требуя отступа для вложенных операторов if ).

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

8.1. Оператор if _

Оператор if используется для условного выполнения:

Он выбирает ровно один из наборов, оценивая выражения одно за другим, пока одно из них не окажется истинным (см. раздел Булевы операции для определения истинности и ложности); затем выполняется этот набор (и никакая другая часть оператора if не выполняется и не оценивается). Если все выражения ложны, выполняется набор предложений else , если они присутствуют.

8.2. Заявление while _

Оператор while используется для повторного выполнения, пока выражение истинно:

Это многократно проверяет выражение и, если оно истинно, выполняет первый набор; если выражение ложно (что может быть при первом тестировании), выполняется набор предложения else , если он присутствует, и цикл завершается.

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

8.3. Заявление for _

Оператор for используется для перебора элементов последовательности (например, строки, кортежа или списка) или другого итерируемого объекта:

Выражение starred_list оценивается один раз; он должен давать итерируемый объект. Итератор создается для этого итерируемого объекта. Затем первый элемент, предоставленный итератором, присваивается целевому списку с использованием стандартных правил присваивания (см. Операторы присваивания ), и набор выполняется. Это повторяется для каждого элемента, предоставленного итератором. Когда итератор исчерпан, набор в предложении else , если он присутствует, выполняется, и цикл завершается.

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

Цикл for выполняет присваивания переменным в списке целей.Это перезаписывает все предыдущие присваивания этим переменным,включая те,которые были сделаны в наборе цикла for:

Имена в целевом списке не удаляются после завершения цикла, но если последовательность пуста, они вообще не будут назначены циклом. Подсказка: встроенная функция range() возвращает итератор целых чисел, подходящий для эмуляции эффекта Паскаля for i := a to b do ; например, list(range(3)) возвращает список [0, 1, 2] .

Изменено в версии 3.11:В списке выражений теперь разрешено использовать элементы со звездочками.

8.4. Заявление о try

Оператор try указывает обработчики исключений и/или код очистки для группы операторов:

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

8.4.1. except пункта

Пункт (ы) exclude указывает один или несколько обработчиков исключений except Когда в предложении try не возникает никаких исключений, обработчик исключений не выполняется. Когда в пакете try возникает исключение, запускается поиск обработчика исключения. Этот поиск по очереди проверяет предложения except , пока не будет найдено то, которое соответствует исключению. except без выражений , если оно присутствует, должно быть последним; он соответствует любому исключению. Для предложения exclude с выражением это выражение оценивается, и предложение соответствует исключению, если результирующий объект «совместим» с исключением except Объект совместим с исключением, если объект является классом или не виртуальным базовым классом .объекта исключения или кортеж, содержащий элемент, который является классом или не виртуальным базовым классом объекта исключения.

Если except не соответствует ни одно предложение exclude, поиск обработчика исключений продолжается в окружающем коде и в стеке вызовов. 1

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

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

except было назначено с помощью as target , оно очищается в конце предложения exclude. Это как если бы

был переведен на

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

До того, как будет выполнен набор предложений except , сведения об исключении хранятся в модуле sys , и к ним можно получить доступ через sys.exc_info() . sys.exc_info() возвращает 3-кортеж, состоящий из класса исключения, экземпляра исключения и объекта трассировки (см. раздел Стандартная иерархия типов ), определяющего точку в программе, где произошло исключение. Подробная информация об исключении, доступ к которому осуществляется через sys.exc_info() , восстанавливается до своих предыдущих значений при выходе из обработчика исключений:

8.4.2. except* оговорки

Пункт (ы) exclude except* используются для обработки ExceptionGroup s. Тип исключения для сопоставления интерпретируется так же, как и в случае с except , но в случае групп исключений мы можем иметь частичное совпадение, когда тип соответствует некоторым исключениям в группе. Это означает, что могут выполняться несколько предложений exclude except* , каждое из которых обрабатывает часть группы исключений. Каждое предложение выполняется не более одного раза и обрабатывает группу исключений из всех соответствующих исключений. Каждое исключение в группе обрабатывается не более чем одним предложением except* , первым, которое ему соответствует.

Любые оставшиеся исключения, которые не были обработаны каким -либо предложением except* , повторно вызываются в конце и объединяются в группу исключений вместе со всеми исключениями, которые были вызваны в предложениях exclude except* .

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

Предложение except* должно иметь соответствующий тип, и этот тип не может быть подклассом BaseExceptionGroup . Невозможно смешивать except и except* в одном и том же try . break , continue и return не могут появляться в предложении except* .

8.4.3. else пункт

Необязательное предложение else выполняется, если поток управления покидает набор try , не было возбуждено исключение и не выполнялись операторы return , continue или break .Исключения в предложении else не обрабатываются предыдущими except .

8.4.4. finally пункт

Если finally присутствует, он указывает обработчик «очистки». Выполняется предложение try , включая все предложения except и else . Если исключение возникает в любом из предложений и не обрабатывается, исключение временно сохраняется. Предложение finally выполняется. Если есть сохраненное исключение, оно повторно вызывается в конце предложения finally . Если предложение finally вызывает другое исключение, сохраненное исключение устанавливается в качестве контекста нового исключения. Если в предложении finally выполняется оператор return , break или continue , сохраненное исключение отбрасывается:

Информация об исключении недоступна для программы во время выполнения предложения finally .

Когда оператор return , break или continue выполняется в наборе try try … finally , предложение finally также выполняется «на выходе».

Возвращаемое значение функции определяется последним выполненным оператором return Поскольку предложение finally выполняется всегда, оператор return , выполняемый в предложении finally , всегда будет последним выполненным:

Изменено в версии 3.8: до Python 3.8 оператор continue был недопустимым в предложении finally из-за проблемы с реализацией.

8.5. Оператор with _

Оператор with используется для переноса выполнения блока на методы, определенные менеджером контекста (см. раздел « Менеджеры контекста операторов » ). Это позволяет инкапсулировать общие шаблоны использования try … except … finally для удобного повторного использования.

Выполнение оператора with с одним «элементом» происходит следующим образом:

  1. Выражение контекста (выражение, указанное в with_item ) оценивается для получения диспетчера контекста.
  2. __enter__() менеджера контекста загружается для последующего использования.
  3. __exit__() менеджера контекста загружается для последующего использования.
  4. __enter__() контекстного менеджера .

Если цель была включена в оператор with , ей присваивается возвращаемое значение из __enter__() .

Оператор with гарантирует, что если метод __enter__() вернется без ошибки, то всегда будет вызываться __exit__() Таким образом, если ошибка возникает во время присвоения целевому списку, она будет обработана так же, как ошибка, возникающая в наборе. См. Шаг 6 ниже.

__exit__() контекстного менеджера . Если исключение вызвало выход из пакета, его тип, значение и трассировка передаются в качестве аргументов __exit__() . В противном случае предоставляются три аргумента None .

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

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

С более чем одним элементом диспетчеры контекста обрабатываются так, как если бы несколько операторов with были вложены:

Вы также можете писать контекстные менеджеры с несколькими элементами в нескольких строках,если элементы окружены круглыми скобками.Например:

Изменения в версии 3.1:Поддержка нескольких контекстных выражений.

Изменения в версии 3.10:Поддержка использования группирующих круглых скобок для разбиения утверждения на несколько строк.

Спецификация, предыстория и примеры для оператора Python with .

8.6. Заявление о match

Новинка в версии 3.10.

Оператор match используется для сопоставления шаблонов.Синтаксис:

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

Сопоставление с шаблоном принимает шаблон в качестве входных данных (следующий case ) и значение темы (после match ). Шаблон (который может содержать подшаблоны) сопоставляется со значением темы. Результаты следующие:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *