Условный оператор if и составные условия

Таблицы истинности

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


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

Таблица истинности оператора NOT

not x Результат
not True False
not False True

Таблицы истинности – общие математические таблицы, которые используются в логике. Их полезно выучить наизусть, чтобы затем применять при построении алгоритмов и написании программ.

Синтаксис и семантика

Исключительные случаи

  • Выражения присваивания, не заключённые в скобки, запрещены на «верхнем» уровне:

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

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

    Не заключенные в скобки выражения присваивания запрещены в значениях ключевого аргумента при вызове функции. Пример:

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

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

    Это правило создано для предотвращения побочных эффектов в местах, где точная семантика и так сбивает с толку многих пользователей (см. Рекомендацию по общему стилю, которая против использования изменяемых «сущностей» в качестве значений по умолчанию).

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

    Рассуждения по поводу введения этого правила аналогичны предыдущим: код, состоящий из комбинации операторов «=» и «:=» трудно правильно понять.

  • Не заключенные в скобки выражения присваивания запрещены в лямбда-функциях. Пример:

    Лямбда-функция имеет приоритет более высокий, чем «:=». Удобное присваивание лямбды к переменной здесь важнее. В случаях, когда переменная используется несколько раз, вам и так (наверняка) понадобятся скобки, потому это ограничение не сильно повлияет на ваш код.

  • Выражения присваивания внутри f-строк требуют скобок. Пример:


    Это показывает, что не всё выглядящее, как оператор присваивания в f-строке, является таковым. Парсер f-строки использует символ «:» для указания параметров форматирования. Чтобы сохранить обратную совместимость, при использовании оператора присваивания внутри f-строк он должен быть заключен в скобки. Как отмечено в примере выше, такое использование оператора присваивания не рекомендуется.

Различия между выражениями присваивания и инструкциями присваивания.

выражениеминструкциивыраженияинструкциях

  • Каскадное присваивание не поддерживается на прямую
  • Отдельные «цели», кроме простого имени переменной NAME, не поддерживаются:
  • Функционал и приоритет «вокруг» запятых отличается:
  • Распаковка и упаковка значений не имеют «чистую» эквивалентность или вообще не поддерживаются
  • Встроенные аннотации типов не поддерживаются:
  • Укороченная форма операций отсутствует:

Порядок вычисления¶

Если имеется выражение вида , что производится раньше: сложение или умножение? Школьный курс математики говорит нам, что умножение должно производиться в первую очередь. Это означает, что оператор умножения имеет более высокий приоритет, чем оператор сложения.

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

Эта таблица взята из и приводится здесь для полноты описания. На практике лучше использовать скобки для группировки операторов и операндов, чтобы в явном виде указать порядок вычисления выражений. Заодно это облегчит чтение программы. Более подробно см. в разделе ниже.

Приоритет операторов

Оператор Описание
лямбда-выражение
Логическое “ИЛИ”
Логическое “И”
Логическое “НЕ”
, Проверка принадлежности
, Проверка тождественности
, , , , , Сравнения
Побитовое “ИЛИ”
Побитовое “ИСКЛЮЧИТЕЛЬНО ИЛИ”
Побитовое “И”
, Сдвиги
, Сложение и вычитание
, , , Умножение, деление, целочисленное деление и остаток от деления
, Положительное, отрицательное
Побитовое НЕ
Возведение в степень
Ссылка на атрибут
Обращение по индексу
Вырезка
Вызов функции
Связка или кортеж
Список
Словарь

Операторы, о которых мы не упомянули, будут объяснены в дальнейших главах.

Битовые операторы в Python

Битовые (или «побитовые«) операторы работают с битами данных, и выполняют операции бит-за-битом. Предположим — и . В бинарном формате они будут выглядеть так:

a = 0011 1100
b = 0000 1101
-----------------
a&b = 0000 1100
a|b = 0011 1101
a^b = 0011 0001
~a  = 1100 0011
Operator Description Example
& Binary AND Operator copies a bit to the result if it exists in both operands. (a & b) will give 12 which is 0000 1100
| Binary OR Operator copies a bit if it exists in eather operand. (a | b) will give 61 which is 0011 1101
^ Binary XOR Operator copies the bit if it is set in one operand but not both. (a ^ b) will give 49 which is 0011 0001
~ Binary Ones Complement Operator is unary and has the efect of ‘flipping’ bits. (~a ) will give -61 which is 1100 0011 in 2’s complement form due to a signed binary number.
<< Binary Left Shift Operator. The left operands value is moved left by the number of bits specified by the right operand. a << 2 will give 240 which is 1111 0000
>> Binary Right Shift Operator. The left operands value is moved right by the number of bits specified by the right operand. a >> 2 will give 15 which is 0000 1111

Отклоненные альтернативны

Альтернативные варианты написания

EXPR as NAME:

Так как конструкция EXPR as NAME уже имеет семантический смысл в выражениях import, except и with, это могло создать ненужную путаницу и некоторые ограничения (например, запрет выражения присваивания внутри заголовков этих конструкций). (Обратите внимание, что «with EXPR as VAR» не просто присваивает значение EXPR в VAR, а вызывает EXPR.__enter__() и уже после присваивает полученный результат в VAR.) Дополнительные причины, чтобы предпочесть «:=» выше предложенному написанию:В том случае, если if f(x) as y не бросится вам в глаза, то его можно ​​случайно прочитать как if f x blah-blah, и визуально такая конструкция слишком похожа на if f(x) and y. Во всех других ситуациях, когда as разрешено, даже читателям со средними навыками приходится прочитывать всю конструкцию от начала, чтобы посмотреть на ключевое слово: import foo as bar except Exc as var with ctxmgr() as var

И наоборот, as не относится к оператором if или while и мы преднамеренно создаём путаницу, допуская использование as в «не родной» для него среде. Также существует «параллель» соответствия междуNAME = EXPR if NAME := EXPR

Это усиливает визуальное распознавание выражений присваивания.

EXPR -> NAME

Этот синтаксис основан на таких языках, как R и Haskell, ну и некоторых программируемых калькуляторах. (Обратите внимание, что направление стрелки справа-налево y

Но эти проблемы совершенно не связано с другим использованием такой стрелки в Python (в аннотациях возвращаемого типа функции), а просто по сравнению с «:=» (которое восходит к Algol-58) стрелочки менее привычны для присваивания. Добавление оператора «точка» к именам локальных переменных

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

Порядок выполнения инвертирован (часть с отступом выполнится первой, а затем последует срабатывание «заголовка»). Это потребует введения нового ключевого слова, хотя возможно «перепрофилирование» другого (скорее всего with:). См. PEP 3150, где раннее обсуждался этот вопрос (предложенным там словом являлось given: ). TARGET from EXPR:

Этот синтаксис меньше конфликтует с другими, чем as (если только не считать конструкции raise Exc from Exc), но в остальном сравним с ними. Вместо параллели с with expr as target: (что может быть полезно, но может и сбить с толку), этот вариант вообще не имеет параллелей ни с чем, но к удивлению лучше запоминается.

as

ТОЛЬКОПреимуществаНедостатки

Особые случаи в генераторах

  1. where, let, or given:

    Этот способ приводит появлению подвыражения между циклом «for» и основным выражением. Он также вводит дополнительное ключевое слово языка, что может создать конфликты. Из трех вариантов, where является наиболее чистым и читабельным, но потенциальные конфликты всё ещё существуют (например, SQLAlchemy и numpy имеют свои методы where, также как и tkinter.dnd.Icon в стандартной библиотеке).

  2. with NAME = EXPR:

    Всё тоже самое, как и в верхнем пункте, но используется ключевое слово with. Неплохо читается и не нуждается в дополнительном ключевом слове. Тем не менее, способ более ограничен и не может быть легко преобразован в «петлевой» цикл for. Имеет проблему языка C, где знак равенства в выражении теперь может создавать переменную, а не выполнять сравнение. Также возникает вопрос: «А почему «with NAME = EXPR:» не может быть использовано просто как выражение, само по себе?»

  3. with EXPR as NAME:

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

with

Переменные и данные

Переменные в питоне не хранят данные, а лишь ссылаются на них, а данные бывают изменяемые (мутабельные) и неизменяемые (иммутабельные). Это приводит к различному поведению в зависимости от типа данных в практически идентичных ситуациях, например такой код:

приводит к тому, что переменные и ссылаются на различные данные, а такой:


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

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

Кстати, о строках, из-за их иммутабельности конкатенация очень большого списка строк сложением или append’ом в цикле может быть не очень эффективной (зависит от рализации в конкретном компиляторе/версии), обычно для таких случаев рекомендуют использовать метод , который ведёт себя немного неожиданно:

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

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

Аналогичная проблема с копированием может возникать при умножении коллекции на скаляр, как разбиралось тут.

Сложные логические выражения

Логические выражения типа являются простыми, так как в них выполняется только одна логическая операция. Однако, на практике нередко возникает необходимость в более сложных выражениях. Может понадобиться получить ответа «Да» или «Нет» в зависимости от результата выполнения двух простых выражений. Например, «на улице идет снег или дождь», «переменная news больше 12 и меньше 20».

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

Чтобы получить True при использовании оператора and, необходимо, чтобы результаты обоих простых выражений, которые связывает данный оператор, были истинными. Если хотя бы в одном случае результатом будет False, то и все сложное выражение будет ложным.

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

Допустим, переменной было присвоено значение 8 (), переменной присвоили 13 (). Логическое выражение будет выполняться следующим образом. Сначала выполнится выражение . Его результатом будет True. Затем выполнится выражение . Его результатом будет False. Далее выражение сведется к , что вернет False.

>>> x = 8
>>> y = 13
>>> y < 15 and x > 8
False

Если бы мы записали выражение так: , то оно также вернуло бы False. Однако сравнение не выполнялось бы интерпретатором, так как его незачем выполнять. Ведь первое простое логическое выражение () уже вернуло ложь, которая, в случае оператора and, превращает все выражение в ложь.

В случае с оператором or второе простое выражение проверяется, если первое вернуло ложь, и не проверяется, если уже первое вернуло истину

Так как для истинности всего выражения достаточно единственного True, неважно по какую сторону от or оно стоит

>>> y < 15 or x > 8
True

В языке Python есть еще унарный логический оператор not, т. е. отрицание. Он превращает правду в ложь, а ложь в правду. Унарный он потому, что применяется к одному выражению, стоящему после него, а не справа и слева от него как в случае бинарных and и or.

>>> not y < 15
False

Здесь возвращает True. Отрицая это, мы получаем False.

>>> a = 5
>>> b = 
>>> not a
False
>>> not b
True

Число 5 трактуется как истина, отрицание истины дает ложь. Ноль приравнивается к False. Отрицание False дает True.

Python Comparison Operators

These operators compare the values on either sides of them and decide the relation among them. They are also called Relational operators.

Assume variable a holds 10 and variable b holds 20, then −

Operator Description Example
== If the values of two operands are equal, then the condition becomes true. (a == b) is not true.
!= If values of two operands are not equal, then condition becomes true. (a != b) is true.
<> If values of two operands are not equal, then condition becomes true. (a <> b) is true. This is similar to != operator.
> If the value of left operand is greater than the value of right operand, then condition becomes true. (a > b) is not true.
< If the value of left operand is less than the value of right operand, then condition becomes true. (a < b) is true.
>= If the value of left operand is greater than or equal to the value of right operand, then condition becomes true. (a >= b) is not true.
<= If the value of left operand is less than or equal to the value of right operand, then condition becomes true. (a <= b) is true.

Lowering operator precedence

There are two logical precedences for the := operator. Either it should bind as loosely as possible, as does statement-assignment; or it should bind more tightly than comparison operators. Placing its precedence between the comparison and arithmetic operators (to be precise: just lower than bitwise OR) allows most uses inside while and if conditions to be spelled without parentheses, as it is most likely that you wish to capture the value of something, then perform a comparison on it:

pos = -1
while pos := buffer.find(search_term, pos + 1) >= 0:
    ...

Once find() returns -1, the loop terminates. If := binds as loosely as = does, this would capture the result of the comparison (generally either True or False), which is less useful.

Отладка регулярных выражений

А вот тут оказалось, что в мире питоне нет инструмента для интерактивной отладки регулярных выражений аналогичного прекрасному перловому модулю Regexp::Debugger (видеопрезентация), конечно есть куча онлайн-инструментов, есть какие-то виндовопроприетарные решения, но для меня это всё не то, возможно стоит использовать перловый инструмент, ибо питонные регэксы не особо отличаются от перловых, напишу инструкцию для невладеющих перловым инструментарием:

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

Examples from the Python standard library

env_base is only used on these lines, putting its assignment on the if moves it as the «header» of the block.

  • Current:

    env_base = os.environ.get("PYTHONUSERBASE", None)
    if env_base:
        return env_base
    
  • Improved:

    if env_base := os.environ.get("PYTHONUSERBASE", None):
        return env_base
    

Avoid nested if and remove one indentation level.

  • Current:

    if self._is_special:
        ans = self._check_nans(context=context)
        if ans:
            return ans
    
  • Improved:

    if self._is_special and (ans := self._check_nans(context=context)):
        return ans
    

Code looks more regular and avoid multiple nested if. (See Appendix A for the origin of this example.)

  • Current:

    reductor = dispatch_table.get(cls)
    if reductor:
        rv = reductor(x)
    else:
        reductor = getattr(x, "__reduce_ex__", None)
        if reductor:
            rv = reductor(4)
        else:
            reductor = getattr(x, "__reduce__", None)
            if reductor:
                rv = reductor()
            else:
                raise Error(
                    "un(deep)copyable object of type %s" % cls)
    
  • Improved:

    if reductor := dispatch_table.get(cls):
        rv = reductor(x)
    elif reductor := getattr(x, "__reduce_ex__", None):
        rv = reductor(4)
    elif reductor := getattr(x, "__reduce__", None):
        rv = reductor()
    else:
        raise Error("un(deep)copyable object of type %s" % cls)
    

tz is only used for s += tz, moving its assignment inside the if helps to show its scope.

  • Current:

    s = _format_time(self._hour, self._minute,
                     self._second, self._microsecond,
                     timespec)
    tz = self._tzstr()
    if tz:
        s += tz
    return s
    
  • Improved:

    s = _format_time(self._hour, self._minute,
                     self._second, self._microsecond,
                     timespec)
    if tz := self._tzstr():
        s += tz
    return s
    

Arithmetic operators

Arithmetic operators are used to perform mathematical operations like addition, subtraction, multiplication, etc.

Operator Meaning Example
+ Add two operands or unary plus x + y+ 2
Subtract right operand from the left or unary minus x — y- 2
* Multiply two operands x * y
Divide left operand by the right one (always results into float) x / y
% Modulus — remainder of the division of left operand by the right x % y (remainder of x/y)
// Floor division — division that results into whole number adjusted to the left in the number line x // y
** Exponent — left operand raised to the power of right x**y (x to the power y)

С этим читают