Содержание
  1. == или is: сравнение объектов по значению и по идентичности
  2. Сравнение списков в Python: порядок, множества, вложенность
  3. Сравнение словарей
  4. Сравнение объектов пользовательского класса: __eq__ и __ne__
  5. Порядок и сортировка: __lt__, __le__, __gt__, __ge__
  6. __hash__: почему при своём __eq__ объект «ломается» в set и dict
  7. dataclass: сравнение объектов без ручных методов
  8. Сравнение с учётом типов
  9. Типичные ошибки: is для строк и чисел — ловушка кэширования
  10. Мини-проект: инструмент сравнения продуктов
  11. Что чем сравнивать: сводная таблица
  12. Частые вопросы
Подборки и сравненияОбновлено · 07.2026

Python: сравнение двух экземпляров пользовательского класса

28 февраля 2024 · 10 минут чтения

Обновлено в июле 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 или свой __eq__
Что выбрать для сравнения: == , is или свой __eq__

Оператор == отвечает на вопрос «равны ли значения», 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 — ещё и операторы порядка для сортировки.

для id="пайтон2" двойной блок курсов не обнаружен

Читайте также

3 материала