Операторы в Python: арифметика, сравнение, is и in

Операторы в Python: арифметика, сравнение, is и in Полезное

Оператор в Python — это символ или ключевое слово, которое выполняет операцию над одним или несколькими значениями. Значения, к которым оператор применяется, называются операндами: в выражении 3 + 5 знак + — оператор, а 3 и 5 — операнды. Ниже разбираю семь групп операторов (арифметические, сравнения, логические, битовые, присваивания, членства, тождественности), правила приоритета и главную ловушку новичка — разницу между == и is. Примеры проверены на Python 3.14.

Оператор и функция — в чем разница

Термины путают, разведу сразу. Оператор — это встроенный синтаксис языка: a + b, x in seq, p is None. Функция — вызываемый объект: len(seq), abs(a), sum(x).

Граница подвижная: почти каждый оператор внутри вызывает «магический» метод. a + b — это a.__add__(b), a == b — это a.__eq__(b). Поэтому один оператор ведет себя по-разному для разных типов: 1 + 2 дает 3, а "1" + "2" дает "12".

Арифметические операторы

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

a, b = 17, 5
print(a + b)   # 22  сложение
print(a - b)   # 12  вычитание
print(a * b)   # 85  умножение
print(a / b)   # 3.4 деление, всегда float
print(a // b)  # 3   целочисленное деление, отбрасывает дробь вниз
print(a % b)   # 2   остаток от деления
print(a ** b)  # 1419857  возведение в степень

Две тонкости, на которых спотыкаются. Первая: / в Python 3 всегда возвращает float, даже когда деление ровное — 10 / 2 даст 5.0, а не 5. Для целого результата берут //.

Вторая: // и % округляют «вниз» (к минус бесконечности), а не «к нулю». На отрицательных числах это заметно.

print(-17 // 5)  # -4, а не -3
print(-17 % 5)   # 3, знак совпадает со знаком делителя (5)

Сохраняется тождество a == (a // b) * b + (a % b): подставив, получаем -17 == (-4)*5 + 3.

Операторы сравнения

Сравнивают операнды и возвращают True или False. Их шесть.

Оператор Значение Пример Результат
== равно по значению 5 == 5.0 True
!= не равно 5 != 4 True
> больше 7 > 3 True
< меньше 2 < 1 False
>= больше или равно 5 >= 5 True
<= меньше или равно 4 <= 3 False

Устаревший знак неравенства <> из Python 2 в третьей версии удален — при попытке использовать его будет SyntaxError. Всегда пишут !=.

Особенность Python: сравнения можно связывать в цепочку. Запись 1 < x < 10 означает 1 < x and x < 10, причем x вычисляется один раз.

x = 5
print(1 < x < 10)   # True
print(3 == 3 == 3)  # True, цепочка работает и для равенства

Логические операторы

Работают с истинностью и возвращают не обязательно bool, а один из операндов. Их три: and, or, not.

print(True and False)  # False
print(True or False)   # True
print(not True)        # False

Важная деталь — «ленивое» вычисление (short-circuit). and останавливается на первом ложном операнде, or — на первом истинном, и возвращают именно этот операнд, а не приведенный к bool результат.

print(0 and 5)     # 0   - and вернул первый ложный операнд
print(2 and 5)     # 5   - первый истинный, поэтому вернулся второй
print(0 or "def")  # def - or вернул первый истинный операнд

Это дает частый идиом — подстановку значения по умолчанию: name = user_input or "гость" присвоит "гость", если user_input — пустая строка. Оговорка: прием ломается, когда допустимое значение само «ложное» (например 0 или ""); тогда берут явную проверку if user_input is None.

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

Работают с числом как с набором двоичных разрядов. Новичку они нужны редко, но пригодятся при работе с флагами, масками и низкоуровневыми протоколами.

Оператор Название Пример (a=6, b=3) Результат
& битовое И a & b 2
\| битовое ИЛИ a \| b 7
^ исключающее ИЛИ a ^ b 5
~ инверсия (унарный) ~a -7
<< сдвиг влево a << 1 12
>> сдвиг вправо a >> 1 3

Разберу на битах: 6 это 0b110, 3 это 0b011.

a, b = 6, 3
print(bin(a & b))  # 0b10  = 2, единицы там, где они в ОБОИХ
print(bin(a | b))  # 0b111 = 7, единицы там, где хотя бы в одном
print(bin(a ^ b))  # 0b101 = 5, единицы там, где РОВНО в одном
print(a << 1)      # 12, сдвиг влево на 1 = умножение на 2
print(a >> 1)      # 3,  сдвиг вправо на 1 = целочисленное деление на 2

Инверсия ~a дает -7, потому что для целых со знаком выполняется тождество ~a == -a - 1. Это не «ошибка», а следствие представления отрицательных чисел в дополнительном коде.

Операторы присваивания

Записывают значение в переменную. Кроме простого = есть составные формы «операция и присваивание».

Оператор Эквивалент Пример
= — a = 7
+= a = a + b a += 3
-= a = a - b a -= 3
*= a = a * b a *= 3
/= a = a / b a /= 3
//= a = a // b a //= 3
%= a = a % b a %= 3
**= a = a ** b a **= 3
&= \|= ^= <<= >>= битовые с присваиванием a <<= 1
total = 10
total += 5   # то же, что total = total + 5
print(total) # 15
total //= 4
print(total) # 3

Отдельно стоит «морж» := (walrus, с Python 3.8) — он присваивает и одновременно возвращает значение, что удобно внутри условий: while (line := input()) != "stop":. Обычное = так не умеет — выражением оно не является.

Операторы членства: in и not in

Проверяют, входит ли элемент в последовательность (строку, список, кортеж, множество) или ключ в словарь. Возвращают bool.

letters = "python"
print("t" in letters)       # True,  подстрока найдена
print("z" in letters)       # False

nums = [10, 20, 30]
print(25 not in nums)       # True,  25 в списке нет
print(20 in nums)           # True

user = {"name": "Ann", "age": 20}
print("name" in user)       # True  - проверяется КЛЮЧ, не значение
print("Ann" in user)        # False - значения так не ищутся

Ключевая оговорка: для словаря in смотрит на ключи. Чтобы искать по значениям, пишут "Ann" in user.values(). Для строки in проверяет вхождение подстроки, а не одного символа: "th" in "python" тоже True.

Операторы тождественности: is и not is

Здесь — самая частая ошибка новичка. == и is спрашивают разное.

  • == сравнивает значения: «равны ли операнды по содержимому» (через __eq__).
  • is сравнивает идентичность: «это буквально один и тот же объект в памяти» (сравнивает id()).
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)  # True  - содержимое совпадает
print(a is b)  # False - это два РАЗНЫХ списка
print(a is c)  # True  - c и a ссылаются на один объект

print(id(a) == id(b))  # False, разные адреса
print(id(a) == id(c))  # True,  один адрес

Практическое правило: с None, True, False сравнивают через is, потому что это объекты-одиночки (в интерпретаторе существует ровно по одному экземпляру). Пишут if x is None, а не if x == None — так надежнее и быстрее. Для сравнения по значению (числа, строки, списки) берут ==.

Ловушка кеширования маленьких чисел

Начинающие «проверяют» is на числах и делают неверный вывод. CPython заранее создает объекты для небольших целых (по реализации — диапазон от -5 до 256) и переиспользует их, поэтому два таких числа гарантированно оказываются одним объектом.

x = 256
y = 256
print(x is y)  # True - оба ссылаются на один кешированный объект

С числами вне этого диапазона результат становится непредсказуемым и зависит от того, как выполняется код. В интерактивном интерпретаторе каждая строка компилируется отдельно, поэтому два литерала 257 — разные объекты:

>>> x = 257
>>> y = 257
>>> x is y
False

А вот те же три строки внутри одного .py-файла дадут True: при компиляции модуля одинаковые константы в пределах одного блока кода схлопываются в один объект. Один и тот же код в REPL и в скрипте дает разный ответ — вот почему на is тут нельзя полагаться.

Вывод один: это деталь реализации CPython, а не гарантия языка — на другом интерпретаторе или в другом контексте результат другой. Поэтому числа никогда не сравнивают через is, только через ==. is — для идентичности объектов, == — для равенства значений.

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

Когда в одном выражении несколько операторов, порядок их выполнения задает приоритет — как в математике, где умножение раньше сложения. Ниже группы от высшего приоритета к низшему (полная таблица есть в документации).

Приоритет Операторы
выше ** (возведение в степень)
~ + - (унарные)
* / // %
+ - (бинарные)
<< >>
& затем ^ затем \|
сравнения == != < > <= >=, а также in is
not
and
ниже or
print(2 + 3 * 4)     # 14, умножение раньше сложения
print((2 + 3) * 4)   # 20, скобки меняют порядок
print(2 ** 3 ** 2)   # 512, ** правоассоциативен: 2 ** (3 ** 2)
print(not 1 == 1)    # False, сравнение раньше not: not (1 == 1)

Две ловушки приоритета: ** правоассоциативна, поэтому 2 ** 3 ** 2 это 2 ** 9 = 512, а не 64; а унарный минус слабее степени — -3 ** 2 равно -9, потому что читается как -(3 ** 2). Когда порядок неочевиден, ставьте скобки.

Выводы

  • Оператор выполняет операцию над операндами; за большинством операторов стоит «магический» метод (+ это __add__, == это __eq__), поэтому поведение зависит от типа.
  • / всегда дает float, а // и % округляют вниз — на отрицательных числах результат неинтуитивен, но подчиняется тождеству a == (a//b)*b + a%b.
  • and/or вычисляются лениво и возвращают операнд, а не bool; это удобно для значений по умолчанию, но осторожно с «ложными» допустимыми значениями.
  • == сравнивает значения, is — идентичность объектов; с None используют is, числа через is не сравнивают из-за кеширования маленьких int в CPython.
  • Приоритет решает порядок вычислений; ** правоассоциативна, унарный минус слабее степени — в спорных местах ставьте скобки.

Где применяется / связь с практикой

Операторы — фундамент любого кода: без них не написать ни условия if age >= 18, ни цикла while count < 10, ни фильтра if item in allowed. Разница == и is, ленивые and/or и приоритет — те места, где ошибки живут молча: код не падает, но считает не то. Поэтому операторы разбирают в самом начале обучения и закрепляют на практике.

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

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

FAQ

Почему 0.1 + 0.2 == 0.3 дает False?
Это не про операторы, а про формат чисел с плавающей точкой: 0.1 и 0.2 хранятся приближенно, их сумма чуть больше 0.3. Для сравнения дробных используют math.isclose(0.1 + 0.2, 0.3).

Чем and отличается от битового &?
and — логический оператор, работает с истинностью и вычисляется лениво. & — битовый, обрабатывает каждый разряд числа отдельно. 5 and 3 даст 3, а 5 & 3 даст 1. Путать их — частая причина неверных условий.

Можно ли определить свой оператор + для своего класса?
Своих символов операторов добавить нельзя, но поведение существующих переопределяется. Реализуйте метод __add__ в классе — и + начнет работать с его экземплярами так, как вы задали. Это называется перегрузкой операторов.

OTUS Журнал