← Назад к вопросам

Как определить что объект хешируемый в Python?

2.0 Middle🔥 151 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Определение хешируемости объекта в Python

Хешируемость — критическое свойство объекта, которое определяет, можно ли его использовать как ключ в словаре или элемент множества (set). Это важный концепт для Python-разработчика.

Что такое hashable?

Объект считается хешируемым, если:

  1. Имеет метод __hash__() (возвращает целое число)
  2. Имеет метод __eq__() (для проверки равенства)
  3. Объект неизменяемый (immutable) — если равные объекты, то одинаковые хеши
  4. Хеш стабилен на протяжении жизни объекта

Способ 1: Проверка наличия hash

# Проверяем наличие метода __hash__
objects_to_test = [
    42,                    # int
    "hello",              # str
    (1, 2, 3),            # tuple
    [1, 2, 3],            # list
    {1, 2, 3},            # set
    {"a": 1},             # dict
]

for obj in objects_to_test:
    is_hashable = hasattr(obj, '__hash__') and obj.__hash__ is not None
    print(f"{type(obj).__name__}: hashable={is_hashable}")
    # int: hashable=True
    # str: hashable=True
    # tuple: hashable=True
    # list: hashable=False
    # set: hashable=False
    # dict: hashable=False

Способ 2: Попытка использовать hash()

def is_hashable(obj):
    """Попытаться вычислить хеш объекта"""
    try:
        hash(obj)
        return True
    except TypeError:
        return False

# Тестирование
test_objects = [
    123,
    "string",
    (1, 2),
    [1, 2],
    {1: 2},
    {1, 2},
    lambda x: x,
]

for obj in test_objects:
    result = is_hashable(obj)
    print(f"{repr(obj):30} -> {result}")
    # 123                            -> True
    # 'string'                       -> True
    # (1, 2)                         -> True
    # [1, 2]                         -> False
    # {1: 2}                         -> False
    # {1, 2}                         -> False
    # <function <lambda>>            -> True

Это самый надёжный способ — практический, а не теоретический.

Способ 3: Проверка типа

from collections.abc import Hashable

object_list = [1, "test", [1, 2], {1, 2}]

for obj in object_list:
    # Проверка через ABC (Abstract Base Class)
    if isinstance(obj, Hashable):
        print(f"{type(obj).__name__} is hashable")
    else:
        print(f"{type(obj).__name__} is NOT hashable")

Но это может быть ненадёжно! Некоторые объекты могут заявлять о хешируемости, но hash() всё равно упадёт.

Способ 4: Проверка hash == None

def check_hashable(obj):
    """Явная проверка через __hash__"""
    # Если __hash__ == None, объект точно не хешируемый
    if hasattr(obj, '__hash__'):
        return obj.__hash__ is not None
    return False

# Классы с явно отключённой хешируемостью
class NonHashable:
    __hash__ = None

class HasEquality:
    def __eq__(self, other):
        return True
    # При определении __eq__, __hash__ автоматически становится None!

print(check_hashable(NonHashable()))  # False
print(check_hashable(HasEquality()))   # False

Практические примеры

Пример 1: Класс с хешем

class Point:
    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y
    
    def __hash__(self):
        return hash((self.x, self.y))
    
    def __eq__(self, other):
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y
    
    def __repr__(self):
        return f"Point({self.x}, {self.y})"

# Проверяем
point = Point(3, 4)
print(f"Hashable: {is_hashable(point)}")  # True

# Можем использовать в set
points_set = {Point(1, 2), Point(3, 4), Point(1, 2)}
print(f"Set size: {len(points_set)}")  # 2 (дубликат удален)

# Можем использовать как ключ в dict
points_dict = {Point(1, 2): "origin", Point(3, 4): "other"}
print(points_dict[Point(1, 2)])  # origin

Пример 2: Класс БЕЗ хеша (когда есть eq)

class User:
    def __init__(self, id: int, name: str):
        self.id = id
        self.name = name
    
    def __eq__(self, other):
        if not isinstance(other, User):
            return NotImplemented
        return self.id == other.id
    
    # __hash__ автоматически становится None!

# Проверяем
user = User(1, "John")
print(f"Hashable: {is_hashable(user)}")  # False

# Не можем использовать в set
try:
    users_set = {user}
except TypeError as e:
    print(f"Ошибка: {e}")  # unhashable type: 'User'

Пример 3: Класс с явным hash

class UserHashable:
    def __init__(self, id: int, name: str):
        self.id = id
        self.name = name
    
    def __eq__(self, other):
        if not isinstance(other, UserHashable):
            return NotImplemented
        return self.id == other.id
    
    def __hash__(self):
        # Хешируем только неизменяемые поля
        return hash(self.id)

# Теперь хешируемый
user = UserHashable(1, "John")
print(f"Hashable: {is_hashable(user)}")  # True

users_set = {UserHashable(1, "John"), UserHashable(2, "Jane")}
print(f"Set size: {len(users_set)}")  # 2

Хешируемые встроенные типы

hashable_types = [
    42,                        # int
    3.14,                      # float
    "hello",                   # str
    b"bytes",                  # bytes
    (1, 2, 3),                 # tuple (если содержит только hashable)
    None,                      # NoneType
    True,                       # bool
    frozenset([1, 2]),         # frozenset
    lambda x: x,               # function
    type,                      # type
]

for obj in hashable_types:
    print(f"{type(obj).__name__:15} hash={hash(obj)}")

Не хешируемые типы

unhashable_types = [
    [1, 2, 3],              # list (изменяемый)
    {1, 2, 3},              # set (изменяемый)
    {"a": 1},               # dict (изменяемый)
    bytearray(b"test"),     # bytearray (изменяемый)
]

for obj in unhashable_types:
    try:
        hash(obj)
    except TypeError as e:
        print(f"{type(obj).__name__:15} -> TypeError: {e}")

Tuple с изменяемыми элементами

# Кортеж сам по себе хешируемый
immutable_tuple = (1, 2, "hello")
print(f"Hashable: {is_hashable(immutable_tuple)}")  # True

# Но если содержит изменяемые объекты...
mixed_tuple = (1, 2, [3, 4])  # содержит list
print(f"Hashable: {is_hashable(mixed_tuple)}")  # False

# Потому что
try:
    hash(mixed_tuple)
except TypeError as e:
    print(f"Ошибка: {e}")  # unhashable type: 'list'

Рекомендация: Всегда тестируйте

def safe_use_in_set(obj):
    """Безопасно ли использовать объект в set?"""
    try:
        test_set = {obj}
        return True
    except TypeError:
        return False

print(safe_use_in_set(42))           # True
print(safe_use_in_set([1, 2]))       # False
print(safe_use_in_set((1, 2)))       # True
print(safe_use_in_set((1, [2])))     # False

Вывод: Самый надёжный способ определить хешируемость — попытаться вычислить hash(obj) и перехватить TypeError. Это практичнее, чем проверять наличие методов.