Содержание
- == или is: сравнение объектов по значению и по идентичности
- Сравнение списков в Python: порядок, множества, вложенность
- Сравнение словарей
- Сравнение объектов пользовательского класса: __eq__ и __ne__
- Порядок и сортировка: __lt__, __le__, __gt__, __ge__
- __hash__: почему при своём __eq__ объект «ломается» в set и dict
- dataclass: сравнение объектов без ручных методов
- Сравнение с учётом типов
- Типичные ошибки: is для строк и чисел — ловушка кэширования
- Мини-проект: инструмент сравнения продуктов
- Что чем сравнивать: сводная таблица
- Частые вопросы
Python: сравнение двух экземпляров пользовательского класса
Черновик готовит редакция с помощью ИИ. За стандарт издания отвечает главный редактор — Валерий Курземнек.
Обновлено в июле 2026.
Сравнение объектов в Python держится на двух операторах: == сравнивает по значению, а is — по идентичности (указывают ли две переменные на один и тот же объект в памяти). Списки оператор == сравнивает поэлементно с учётом порядка; чтобы сравнить состав без порядка, приводите к set(). Для своих классов правило равенства задаёт метод __eq__.
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True — значения совпадают
a is b # False — это два разных объекта
set(a) == set([3, 2, 1]) # True — состав без учёта порядка
Дальше разбираем каждый случай: сравнение списков, словарей и экземпляров пользовательского класса, роль __eq__ и __hash__, готовое сравнение через dataclass и ловушки, на которых спотыкаются даже опытные.
==— равенство по значению;is— тот же объект в памяти.- Списки:
==учитывает порядок;set()— нет;collections.Counter— учитывает количество повторов. - Словари:
==сравнивает пары ключ-значение, порядок ключей не важен. - Свои классы: без
__eq__объекты сравниваются по идентичности; определили__eq__— определите и__hash__. @dataclassгенерирует__eq__сам, а сorder=True— и операторы порядка.
== или is: сравнение объектов по значению и по идентичности

Оператор == отвечает на вопрос «равны ли значения», is — на вопрос «это буквально один и тот же объект». Это разные вопросы, и путаница между ними — источник трудноуловимых багов.
x = [1, 2, 3]
y = x # та же ссылка
z = list(x) # новая копия с тем же содержимым
x == z # True — значения равны
x is z # False — объекты разные
x is y # True — одна и та же ссылка
По умолчанию object реализует __eq__ через is: если класс не переопределил равенство, == и is для его экземпляров дают один результат — сравнение по идентичности. Отдельный случай — сравнение с None: None в программе существует в единственном экземпляре, поэтому корректная проверка всегда if value is None, а не == None.
Вывод: значения сравнивайте через ==, а is держите для синглтонов (None, True, False) и проверки «тот же ли это объект».
Сравнение списков в Python: порядок, множества, вложенность
Для списков == работает поэлементно и слева направо: списки равны, если у них одинаковая длина и на каждой позиции равные элементы. Порядок важен.
['nginx', 'apache', 'mysql'] == ['nginx', 'apache', 'mysql'] # True
['nginx', 'apache', 'mysql'] == ['apache', 'nginx', 'mysql'] # False — порядок другой
Если порядок не важен, а важен состав, есть три инструмента под разные задачи:
- set() — «одинаковый ли набор элементов». Игнорирует и порядок, и дубли:
set([1, 2, 2]) == set([2, 1])вернётTrue. - collections.Counter — «одинаковый ли состав с учётом количества повторов».
Counter([1, 2, 2]) == Counter([2, 1])вернётFalse. - sorted() — сравнение отсортированных копий, когда элементы сравнимы между собой.
from collections import Counter
a, b = [1, 2, 2, 3], [3, 2, 1, 2]
set(a) == set(b) # True — набор совпадает
Counter(a) == Counter(b) # True — и повторов поровну
sorted(a) == sorted(b) # True
Вложенные списки == сравнивает рекурсивно — до самого дна, поэтому [[1, 2], [3]] == [[1, 2], [3]] корректно даёт True. А вот set() к вложенным спискам применить нельзя: список нехешируем и внутрь множества не попадёт — для этого элементы приводят к кортежам.
Вывод: == для точного совпадения с порядком, set() — для набора, Counter — когда важны повторы.
Сравнение словарей
Словари оператор == сравнивает по парам ключ-значение. Порядок ключей на равенство не влияет, значения сравниваются рекурсивно.
{'a': 1, 'b': 2} == {'b': 2, 'a': 1} # True — порядок ключей не важен
{'a': 1} == {'a': 1, 'b': 2} # False — разный набор ключей
Чтобы понять, чем именно отличаются два словаря, удобно сравнить множества их элементов: d1.items() ^ d2.items() покажет расходящиеся пары (работает, если значения хешируемы).
Сравнение объектов пользовательского класса: __eq__ и __ne__
Для своих классов Python «из коробки» не знает, как сравнивать экземпляры, и по умолчанию сравнивает их по идентичности. Два объекта с одинаковыми атрибутами окажутся неравны, пока вы не определите метод __eq__.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
Product("Мышь", 500) == Product("Мышь", 500) # False без __eq__
Определите __eq__ — и == начнёт сравнивать по нужным вам атрибутам. Метод __ne__ (!=) Python выводит из __eq__ автоматически, отдельно писать его не нужно.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __eq__(self, other):
if not isinstance(other, Product):
return NotImplemented
return (self.name, self.price) == (other.name, other.price)
Возврат NotImplemented для чужих типов — не формальность: он даёт Python шанс попробовать сравнение с другой стороны и не сравнивать Product со строкой как попало.
Порядок и сортировка: __lt__, __le__, __gt__, __ge__
Чтобы объекты можно было упорядочивать (<, >, sorted(), max()), реализуют методы сравнения по величине. Каждый возвращает True или False.
__lt__— меньше (<)__le__— меньше или равно (<=)__gt__— больше (>)__ge__— больше или равно (>=)
def __lt__(self, other):
return self.price < other.price
def __le__(self, other):
return self.price <= other.price
def __gt__(self, other):
return self.price > other.price
def __ge__(self, other):
return self.price >= other.price
Чтобы не писать все четыре вручную, добавьте декоратор @functools.total_ordering: определите __eq__ и один из операторов порядка, остальные Python достроит сам.
__hash__: почему при своём __eq__ объект «ломается» в set и dict
Тут прячется ловушка, о которой узнают, когда объект внезапно перестаёт работать как ключ словаря или элемент множества. Официальная документация формулирует правило прямо: «objects which compare equal have the same hash value» — равные объекты обязаны иметь одинаковый хеш.
Из этого следует жёсткое поведение: класс, который переопределил __eq__ и не определил __hash__, получает __hash__, неявно установленный в None. Его экземпляры становятся нехешируемыми и вызывают TypeError при попытке положить их в set или использовать ключом dict.
class Product:
def __init__(self, name, price):
self.name, self.price = name, price
def __eq__(self, other):
return (self.name, self.price) == (other.name, other.price)
{Product("Мышь", 500)} # TypeError: unhashable type: 'Product'
Решение — определить __hash__ согласованно с __eq__: хеш строят из тех же полей, по которым идёт равенство (см. документацию по модели данных Python).
def __hash__(self):
return hash((self.name, self.price))
Вывод: переопределяете __eq__ — сразу решайте судьбу __hash__. Нужен объект-ключ — стройте хеш из тех же полей; объект изменяемый — оставьте его нехешируемым осознанно.
dataclass: сравнение объектов без ручных методов
Начиная с Python 3.7 модуль dataclasses снимает почти всю ручную работу. Декоратор @dataclass по умолчанию (eq=True) генерирует __eq__, который сравнивает экземпляры поле за полем; при этом оба объекта должны быть одного типа.
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: int
Product("Мышь", 500) == Product("Мышь", 500) # True — __eq__ сгенерирован
Параметр order=True (по умолчанию False) дополнительно генерирует __lt__, __le__, __gt__, __ge__ — они сравнивают объект «как кортеж его полей по порядку». Это даёт сортировку без единой строки ручного кода.
@dataclass(order=True)
class Product:
price: int
name: str
sorted([Product(500, "Мышь"), Product(300, "Кабель")])
# отсортировано по price, затем по name
Хеш dataclass генерирует по документированным правилам: если eq=True и frozen=True — генерируется __hash__; если eq=True, а frozen=False — __hash__ ставится в None (объект изменяем, значит нехешируем). Нужен неизменяемый объект-ключ — используйте @dataclass(frozen=True).
Сравнение с учётом типов
Python не приводит типы «на всякий случай»: 1 == 1.0 даёт True (числа сравниваются по значению), но 1 == "1" — False, без ошибки. Поэтому в __eq__ явно проверяйте тип через isinstance и возвращайте NotImplemented для несовместимых объектов — иначе получите либо ложные равенства, либо падение на чужих данных. Списки и кортежи с равным содержимым тоже не равны: [1, 2] == (1, 2) — это False.
Типичные ошибки: is для строк и чисел — ловушка кэширования
Самый коварный баг — использование is для сравнения чисел и строк. Иногда оно «работает» и подкрепляет ложную уверенность:
a = 256
b = 256
a is b # True — но это случайность
x = 1000
y = 1000
x is y # False — хотя значения равны
Причина в оптимизации CPython: интерпретатор заранее кэширует небольшие целые числа (примерно от -5 до 256) и часть строк, поэтому для них is случайно совпадает с ==. За пределами кэша совпадение исчезает, и код, написанный на is, начинает вести себя непредсказуемо в зависимости от значений. Это деталь реализации CPython, полагаться на неё нельзя.
Другие частые промахи: сравнивать float через == без допуска (используйте math.isclose); переопределить __eq__ и забыть про __hash__; сравнивать объекты разных типов без isinstance.
Мини-проект: инструмент сравнения продуктов
Соберём знания в маленьком инструменте, который сравнивает товары по цене. Определяем dataclass с порядком и сразу получаем и равенство, и сортировку.
from dataclasses import dataclass
@dataclass(order=True, frozen=True)
class Product:
price: int
name: str
products = [
Product(1200, "Ноутбук"),
Product(800, "Смартфон"),
Product(600, "Планшет"),
]
most_expensive = max(products)
print(f"Самый дорогой: {most_expensive.name}")
same_price = products[0] == products[1]
print(f"Цены первых двух равны? {'Да' if same_price else 'Нет'}")
За счёт frozen=True объекты ещё и хешируемы — их можно класть в множество, чтобы отсеять дубликаты по всем полям одной строкой: set(products).
Что чем сравнивать: сводная таблица
| Задача | Инструмент | Учитывает |
|---|---|---|
| Равны ли значения | == |
значение, тип |
| Тот же ли это объект | is |
идентичность в памяти |
| Списки полностью совпадают | == |
порядок и повторы |
| Одинаковый набор элементов | set(a) == set(b) |
только состав |
| Состав с учётом повторов | Counter(a) == Counter(b) |
состав и количество |
| Словари равны | == |
пары ключ-значение |
| Свои объекты по атрибутам | __eq__ (+ __hash__) |
выбранные поля |
| Свои объекты без ручного кода | @dataclass |
все поля |
| Сортировка объектов | __lt__…__ge__ или order=True |
поля по порядку |
| Числа с плавающей точкой | math.isclose |
значение с допуском |
Практика закрепляет это быстрее любой теории: возьмите свой класс, добавьте __eq__ и __hash__, а потом перепишите его на @dataclass и сравните объём кода.
Частые вопросы
Чем отличается == от is в Python?
== сравнивает объекты по значению (равны ли данные), is — по идентичности (один ли это объект в памяти). Для значений всегда используйте ==; is оставьте для None, True и False.
Как сравнить два списка в Python?
Оператор == сравнивает списки поэлементно с учётом порядка. Если порядок не важен, приведите к set(); если важно ещё и количество повторов — используйте collections.Counter.
Почему объекты моего класса не равны при одинаковых полях?
Без метода __eq__ Python сравнивает экземпляры по идентичности, поэтому два разных объекта неравны даже с одинаковыми атрибутами. Определите __eq__ или примените @dataclass.
Почему объект вызывает TypeError unhashable в set или dict?
Класс переопределил __eq__, но не задал __hash__ — тогда __hash__ неявно становится None. Определите __hash__ из тех же полей, что и __eq__, или используйте @dataclass(frozen=True).
Можно ли сравнивать объекты через is для строк и чисел?
Нет. Для маленьких чисел и некоторых строк is случайно совпадает с == из-за кэширования в CPython, но для других значений даёт False. Сравнивайте значения только через ==.
Как сравнить объекты по атрибутам без написания методов?
Используйте @dataclass: он сам генерирует __eq__ по всем полям, а с order=True — ещё и операторы порядка для сортировки.
