Логические операторы в Python: and, or, not, короткое замыкание и типичные ошибки

Логические операторы в Python: and, or, not, короткое замыкание и типичные ошибки

Логические операторы в Python: and, or, not, короткое замыкание и типичные ошибки

Запрос для этой статьи: «логические операторы в Python». Если вы уверенно понимаете, как работают and, or, not, почему они иногда возвращают не булевы значения и как устроено короткое замыкание, вы сразу пишете чище и надёжнее. Разберём всё на практических примерах, с советами и частыми ошибками.

Быстрый обзор: and, or, not и «истинность» объектов

В Python любое выражение имеет «истинность» (truthiness): пустые контейнеры, 0, пустая строка, None — ложны; непустые объекты — истинны. Проверить можно функцией bool().

bool(0)        # False
bool(1)        # True
bool("")       # False
bool("hi")     # True
bool([])       # False
bool([1])      # True
bool(None)    # False

Операторы работают так:

  • not x — унарный оператор, возвращает True, если x ложен, иначе False.
  • x and y — если x ложен, возвращает x, иначе возвращает y.
  • x or y — если x истинен, возвращает x, иначе возвращает y.
  • Обратите внимание: and/or возвращают сам операнд, а не обязательно булево значение!

    [] or [1]          # [1]
    0 or 5             # 5
    "" or "fallback"    # "fallback"
    "hi" and 42        # 42
    "" and "x"         # ""
    

    Короткое замыкание (short-circuit) и порядок вычисления

    Python останавливает вычисления, как только результат ясен:

  • В x and y при ложном x выражение y даже не вычисляется.
  • В x or y при истинном x выражение y не вычисляется.
  • def left():
        print("left")
        return False
    
    def right():
        print("right")
        return True
    
    left() and right()  # выведет: left (right не вызовется)
    right() or left()   # выведет: right (left не вызовется)
    

    Практическая польза: можно безопасно вызывать «дорогие» функции только при необходимости.

    user and user.is_active and send_bonus(user)  # send_bonus вызовется только для активного пользователя
    

    Шаблоны: «дефолтные значения» и безопасные проверки

    Популярный трюк — задать значение по умолчанию через or:

    username = input("Имя: ") or "Гость"
    

    Важно: это срабатывает для любого ложного значения («», 0, [] …). Если хотите заменить только None, используйте явную проверку:

    value = None
    result = value if value is not None else 10  # 0 или "" сохраняются как есть
    

    any и all — удобнее, чем длинные связки and/or

    any(iterable) — True, если хотя бы один элемент истинен; all(iterable) — True, если все истинны. Они тоже коротко замыкаются и принимают генераторы (без создания списков).

    nums = [0, 3, 5, 0]
    any(n > 4 for n in nums)   # True
    all(n >= 0 for n in nums)  # True
    
    # Валидация формы
    name, email = "Alex", ""
    if all([name, email]):
        print("OK")
    else:
        print("Заполните все поля")
    

    Совет: с большими данными используйте генераторы: all(cond(x) for x in data) — так не тратится память на список.

    Цепочки сравнений: читаемо и без лишних повторов

    Python поддерживает «математический» синтаксис цепочек: 0 <= x < 10 эквивалентно (0 <= x) and (x < 10), при этом x вычисляется один раз.

    x = 5
    0 <= x < 10     # True
    x == 1 == 1     # False (эквивалентно x == 1 and 1 == 1)
    3 <= x != 7     # True (эквивалентно 3 <= x and x != 7)
    

    Используйте цепочки для диапазонов и каскадных проверок, это и короче, и безопаснее.

    Приоритет операторов: not > and > or

    Важно знать порядок вычисления: not выше, чем and, а and выше, чем or. Сравнения имеют приоритет выше, чем логические операторы. Поэтому:

    not True or False    # (not True) or False  -> False
    True and False or True  # (True and False) or True -> True
    not a == b           # то же, что not (a == b)
    (a != b)             # эквивалентно более читаемо
    

    С сомнениями — ставьте скобки. Так вы избавитесь от неоднозначности и ошибок чтения.

    Логические vs побитовые: and/or против & и |

    Не путайте логические операторы с побитовыми для чисел и элементно‑логическими для массивов (NumPy/Pandas).

  • and/or работают с истинностью объектов и коротко замыкаются.
  • & и | — побитовые/поэлементные, не делают короткого замыкания.
  • # Числа: побитовые операции
    5 & 3   # 1
    5 | 2   # 7
    
    # NumPy/Pandas: объединение булевых масок — только & и |, и ОБЯЗАТЕЛЬНО скобки!
    # (df["a"] > 0) & (df["b"] < 5) — правильно
    # df["a"] > 0 & df["b"] < 5   — ошибка из-за приоритета операторов
    

    Векторные библиотеки запрещают and/or для массивов, т.к. «истинность» массива неоднозначна — используйте & и | со скобками.

    Частые ошибки и как их избежать

  • Сравнение с True/False: if is_ready == True: — избыточно. Пишите if is_ready: или if not is_ready:.
  • Подмена легитимных «ложных» значений: x or default заменит 0, «», [] и т.д. Если нужно заменить только None — x if x is not None else default.
  • «and-or» как тернарный оператор: a and b or c ломается, когда b ложен (возвращается c). Используйте b if a else c.
  • Сравнение с None: if x == None — неверный стиль. Пишите x is None или x is not None.
  • Ошибка «x == ‘a’ or ‘b’»: это всегда истинно, т.к. 'b' — истинный операнд. Правильно: x in ('a', 'b').
  • not in: пишите x not in y, а не not x in y — так читаемее и соответствует грамматике языка.
  • x = "b"
    x == "a" or "b"   # True (не то, что вы хотите)
    x in ("a", "b")   # Правильно
    
    value = 0
    value or 10         # 10 (вдруг 0 — валидное значение!)
    value if value is not None else 10  # 0 сохранится
    

    Идиомы и микропаттерны с логическими операторами

  • Гвард-блоки: быстро выйти при некорректных условиях.
  • def send(user):
        if not user or not user.email:
            return  # нечего делать
        # основная логика
    
  • Ленивая инициализация:
  • config = cached_config or load_config_from_file()  # файл читается только при отсутствии кеша
    
  • Компактная валидация:
  • def is_valid_user(u):
        return all([u.name, u.email, u.age and u.age >= 18])
    
  • Безопасные цепочки вызовов (когда может быть None):
  • # Возьмём домен email, если user и email существуют
    domain = user and user.email and user.email.split("@")[-1]
    # Лучше — тернарный/паттерн проверки:
    domain = user.email.split("@")[-1] if (user and user.email) else None
    

    Немного о производительности

    Короткое замыкание экономит время и ресурсы. Простой пример с timeit:

    import timeit
    setup = """
    def heavy():
        s = 0
        for i in range(10_000):
            s += i*i
        return s
    x = 0
    """
    print(timeit.timeit("x and heavy()", setup=setup, number=1000))  # heavy() почти не вызывается
    print(timeit.timeit("x or heavy()", setup=setup, number=1000))   # heavy() вызывается всегда (x=0)
    

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

    Чек‑лист по логическим операторам в Python

  • Помните: and/or возвращают не True/False, а операнды.
  • Используйте короткое замыкание для «ленивых» вычислений.
  • Для «дефолтов» через or учитывайте ложные, но валидные значения (0, «»).
  • Для массива условий — any/all с генераторами.
  • Для диапазонов — цепочки сравнений: 0 <= x < 10.
  • Не путайте and/or с побитовыми & и |; в Pandas/NumPy нужны & и | со скобками.
  • Проверки с None делайте через is/is not.
  • Сомневаетесь в приоритете — ставьте скобки.
  • Что дальше

    Если хотите системно прокачать основы, практиковаться на задачах и быстро перейти к реальным проектам, посмотрите пошаговый курс «Python с Нуля до Гуру» — стартуйте уже сегодня. Курс структурирован от базовых концепций до продвинутых приёмов, с практикой и обратной связью.

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

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий