Преобразовать набор в тип hashable - PullRequest
0 голосов
/ 17 апреля 2019

У меня есть некоторые переменные, которые связаны два на два, и я хочу хранить их как атрибуты объектов Siblings ().Итак, я создаю класс:

def Siblings(object):
    def __init__(self, brother, sister):
        self._brother = brother
        self._sister = sister

    def __eq__(self, other):
        if isinstance(other, Siblings):
            return {self._brother, self._sister} == {other._brother, other._sister}

    def __hash__(self):
        return hash((self._brother, self._sister)

Причина, по которой я использую наборы в eq , заключается в том, что я не знаю (и мне все равно), какая переменная будет считаться братоми который как сестра.На самом деле,

s1 = Siblings(1,2)
s2 = Siblings(2,1)
print(s1 == s2)

печатает True, что мне и нужно.Проблема состоит в том, что мои объекты Siblings () должны использоваться как словарные ключи, так что упомянутые выше s1 и s2 соответствуют одному и тому же ключу.Однако:

a = {s1: 5}
a[s2] = 4

не изменяет значение 5, как я хочу, а вместо этого добавляет новую пару ключ-значение.Это понятно по определению моего хеша .

Я попытался заменить вектор на набор из хеша , но я получил TypeError: unhashable type: 'задавать'.Любые предложения о том, как решить эту проблему?

Ответы [ 3 ]

2 голосов
/ 17 апреля 2019

Хеш с frozenset вместо кортежа:

def __hash__(self):
    return hash(frozenset([self._brother, self._sister]))

Не имеет отношения к вопросу, но рекомендуется - вам следует избегать возврата None в __eq__:

def __eq__(self, other):
    if isinstance(other, Siblings):
        return {self._brother, self._sister} == {other._brother, other._sister}
    return NotImplemented

Обратите внимание, что хэш не должен изменяться в пределах временной шкалы объекта. Пока вы не пишете в атрибуты _brother и _sister после init, вы должны быть в безопасности.

2 голосов
/ 17 апреля 2019

Python имеет встроенный набор хэшей под названием frozenset. Он неизменен, поэтому, если вам нужно изменить его, вам нужно создать новый объект (как с кортежами).

a = frozenset([1, 2])
b = frozenset([2, 1])
a == b
# True

d = {a: "a"}
# {frozenset({1, 2}): 'a'}
d[b] = "b"
# {frozenset({1, 2}): 'b'}

a.add(3) 
# AttributeError: 'frozenset' object has no attribute 'add'

a = a | {3}
# frozenset({1, 2, 3})
1 голос
/ 17 апреля 2019

Как уже указывалось в других ответах, использование frozenset является допустимым подходом, еще один полезный выбор для таких случаев - использование namedtuples , пример ниже:

from collections import namedtuple

BaseSiblings = namedtuple('Siblings', 'brother sister')


class Siblings(BaseSiblings):

    def __hash__(self):
        return super().__hash__()

    def __eq__(self, other):
        if isinstance(other, Siblings):
            return {self.brother, self.sister} == {other.brother, other.sister}

        return NotImplemented

obj1 = Siblings('a', 'b')
print(obj1)
obj2 = Siblings('b', 'a')
print(obj2 == obj1)
print({obj1: '1'} == {obj2: '1'})
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...