Диктовки, що розширюються Python


94

Як вправу, і в основному для власної розваги, я впроваджую синтаксичний аналізатор для зворотного відстеження. Натхненням для цього є те, що я хотів би мати кращу ідею про те, як будуть працювати гігенічні макроси в алголіноподібній мові (на відміну від діалектів без синтаксису, які ви зазвичай знайдете). Через це різні проходи через вхід можуть бачити різні граматики, тому результати кешованого аналізу недійсні, якщо я також не зберігаю поточну версію граматики разом із кешованими результатами синтаксичного аналізу. ( РЕДАКТУВАТИ : наслідком такого використання колекцій ключ-значення є те, що вони повинні бути незмінними, але я не маю наміру виставляти інтерфейс, щоб дозволити їх змінювати, тому або змінні, або незмінні колекції чудово)

Проблема в тому, що диктовки python не можуть відображатися як ключі до інших диктовок. Навіть використання кортежу (як я б це робив у будь-якому випадку) не допомагає.

>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> 

Я здогадуюсь це повинно бути кортежі весь шлях вниз. Тепер стандартна бібліотека python надає приблизно те, що мені потрібно, collections.namedtupleмає зовсім інший синтаксис, але може використовуватися як ключ. продовжуючи зверху сесію:

>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}

В порядку. Але я повинен створити клас для кожної можливої ​​комбінації ключів у правилі, яке я хотів би використовувати, що не так вже й погано, оскільки кожне правило синтаксичного аналізу точно знає, які параметри воно використовує, щоб цей клас можна було визначити одночасно як функція, яка аналізує правило.

Змінити: Додатковою проблемою namedtuples є те, що вони суворо позиційні. Два кортежі, які здаються різними, насправді можуть бути однаковими:

>>> you = namedtuple("foo",["bar","baz"])
>>> me = namedtuple("foo",["bar","quux"])
>>> you(bar=1,baz=2) == me(bar=1,quux=2)
True
>>> bob = namedtuple("foo",["baz","bar"])
>>> you(bar=1,baz=2) == bob(bar=1,baz=2)
False

tl'dr: Як отримати dicts, які можна використовувати як ключі до інших dicts?

Трохи зламавши відповіді, ось більш повне рішення, яке я використовую. Зверніть увагу, що це робить трохи додаткової роботи, щоб зробити отримані дикти неясно незмінними для практичних цілей. Звичайно, це все ще досить просто зламати, зателефонувавши, dict.__setitem__(instance, key, value)але ми всі тут дорослі.

class hashdict(dict):
    """
    hashable dict implementation, suitable for use as a key into
    other dicts.

        >>> h1 = hashdict({"apples": 1, "bananas":2})
        >>> h2 = hashdict({"bananas": 3, "mangoes": 5})
        >>> h1+h2
        hashdict(apples=1, bananas=3, mangoes=5)
        >>> d1 = {}
        >>> d1[h1] = "salad"
        >>> d1[h1]
        'salad'
        >>> d1[h2]
        Traceback (most recent call last):
        ...
        KeyError: hashdict(bananas=3, mangoes=5)

    based on answers from
       http://stackoverflow.com/questions/1151658/python-hashable-dicts

    """
    def __key(self):
        return tuple(sorted(self.items()))
    def __repr__(self):
        return "{0}({1})".format(self.__class__.__name__,
            ", ".join("{0}={1}".format(
                    str(i[0]),repr(i[1])) for i in self.__key()))

    def __hash__(self):
        return hash(self.__key())
    def __setitem__(self, key, value):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def __delitem__(self, key):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def clear(self):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def pop(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def popitem(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def setdefault(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def update(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    # update is not ok because it mutates the object
    # __add__ is ok because it creates a new object
    # while the new object is under construction, it's ok to mutate it
    def __add__(self, right):
        result = hashdict(self)
        dict.update(result, right)
        return result

if __name__ == "__main__":
    import doctest
    doctest.testmod()

Він hashdictповинен бути незмінним, принаймні після того, як ви почнете його хешувати, так чому б не кешувати значення keyта hashзначення як атрибути hashdictоб'єкта? Я модифікував __key()і __hash__(), і протестував, щоб підтвердити, що це набагато швидше. ТАК не дозволяє форматований код у коментарях, тому я зв’яжу
Сем Уоткінс

Відповіді:


71

Ось простий спосіб створити розширюваний словник. Тільки пам’ятайте, що не мутуйте їх після вбудовування в інший словник зі зрозумілих причин.

class hashabledict(dict):
    def __hash__(self):
        return hash(tuple(sorted(self.items())))

7
Це не забезпечує чіткого узгодження еквалайзера та хешу, тоді як моя попередня відповідь робить за допомогою методу __key (на практиці будь-який з підходів повинен працювати, хоча цей може бути уповільнений шляхом створення непотрібного ідентифікаційного списку - виправлення s / items / iteritems / - припускаючи Python 2. *, як ви не говорите ;-).
Алекс Мартеллі,

5
Можливо, було б краще просто використовувати заморожений набір, а не кортеж із сортуванням. Це не тільки було б швидше, але ви не можете припустити, що ключі словника порівнянні.
asmeurer

1
Здається, повинен бути спосіб уникнути хеш-функції, O(n*log(n))де і nє кількість dictзаписів. Хтось знає, чи frozensetхеш-функція Python працює за лінійний час?
Том Карзес,

2
@HelloGoodbye Дикт також можна створити таким dict(key1=value1, key2=value2,...)чи цим dict([(key1, value1), (key2, value2),...)]). Те саме стосується і цього. Опубліковане вами творіння називається буквальним
smido

2
@smido: Дякую. Я також виявив, що ви можете просто кинути літерал, тобто hashabledict({key_a: val_a, key_b: val_b, ...}).
HelloGoodbye

62

Хашаблі повинні бути незмінними - не застосовуючи цього, але ДОВІРАЮЧИ вам, що ви не мутуєте дикт після його першого використання в якості ключа, працює наступний підхід:

class hashabledict(dict):
  def __key(self):
    return tuple((k,self[k]) for k in sorted(self))
  def __hash__(self):
    return hash(self.__key())
  def __eq__(self, other):
    return self.__key() == other.__key()

Якщо ТЕБЕ потрібно мутувати свої диктовки і ПОВНІ хочете використовувати їх як ключі, складність вибухає в сто крат - не сказати, що цього неможливо зробити, але я зачекаю ДУЖЕ конкретних ознак, перш ніж я потраплю в ТУ неймовірну болота! -)


Я, звичайно, не хочу мутувати диктовки, як тільки вони будуть підготовлені. Це призведе до того, що решта алгоритму packrad розпадеться.
SingleNegationElimination

Тоді підклас, який я запропонував, буде працювати - зауважте, як він обходить "позиційне" питання ( до того, як ви відредагували своє запитання, щоб вказати на нього ;-) за допомогою sortedin __key ;-).
Алекс Мартеллі,

Позиційна залежність поведінки namedtuple здивувала мене. Я бавився з цим, думаючи, що це може бути все-таки простішим способом вирішити проблему, але це в значній
мірі зруйнувало

Скажімо, у мене є дикт і я хочу передати його хашаблдикту. Як би я це зробив?
jononomo

@JonCrowell побачити ці питання для ідей і уточнень: stackoverflow.com/questions/3464061 / ... , stackoverflow.com/questions/9112300 / ... , stackoverflow.com/questions/18020074 / ...
більш

32

Все, що потрібно, щоб зробити словники придатними для вашої мети, - це додати метод __hash__:

class Hashabledict(dict):
    def __hash__(self):
        return hash(frozenset(self))

Зверніть увагу, заморожений набір перетворення буде працювати для всіх словників (тобто воно не вимагає сортування ключів). Так само, немає обмежень на значення словника.

Якщо є багато словників з однаковими ключами, але з різними значеннями, необхідно, щоб хеш враховував значення. Найшвидший спосіб зробити це:

class Hashabledict(dict):
    def __hash__(self):
        return hash((frozenset(self), frozenset(self.itervalues())))

Це швидше, ніж frozenset(self.iteritems())з двох причин. По-перше, frozenset(self)крок повторно використовує хеш-значення, що зберігаються у словнику, зберігаючи непотрібні дзвінки hash(key). По-друге, використання itervalues дозволить отримати безпосередній доступ до значень і уникнути безлічі викликів розподілювача пам'яті, використовуваних by items, щоб формувати нові безліч наборів ключів / значень в пам'яті кожного разу, коли ви виконуєте пошук.


@RaymondHettinger Виправте мене, якщо я помиляюся, але я подумав, що dictсам не кешує хеш-значення своїх ключів - хоча окремі класи (як str) можуть і вирішують кешувати свої хеші. Принаймні, коли я створив a dictзі своїми екземплярами власного класу, що використовуються як ключі, їх__hash__ методи викликалися при кожній операції доступу (python 3.4). Незалежно від того, чи я правильно, я не впевнений, як hash(frozenset(self))можна повторно використовувати заздалегідь обчислені хеш-значення, якщо вони не кешовані всередині самих ключів (у такому випадку hash(frozenset(self.items())вони також використовуються повторно).
більше

Що стосується Вашого другого пункту щодо створення кортежу (ключ / значення), я думав, що методи .items () повертають подання, а не список кортежів, і що створення цього подання не передбачає копіювання основних ключів та значень. (Знову Python 3.4.) Тим не менше, я бачу перевагу хешування лише клавіш, якщо більшість входів мають різні ключі - тому що (1) хеш-значення досить дорого, і (2) вимагати, щоб значення хешували
більше

6
Це також має можливість створити однаковий хеш для двох різних словників. Поміркуйте {'one': 1, 'two': 2}і{'one': 2, 'two': 1}
AgDude

Майк Грем у своєму коментарі стверджує, що висувати дикт з будь-якої іншої причини, крім як визначити, __missing__є поганою ідеєю. Що ти думаєш?
Piotr Dobrogost

1
Підкласи з dict були чітко визначені ще з Python 2.2. Див. Collection.OrderedDict та колекції.Counter для прикладів зі стандартної бібліотеки Python. Інший коментар базується на необгрунтованому переконанні, що лише підкласи MutableMapping чітко визначені.
Реймонд Хеттінгер,

23

Наведені відповіді нормальні, але їх можна покращити, використовуючи frozenset(...)замість tuple(sorted(...))генерування хешу:

>>> import timeit
>>> timeit.timeit('hash(tuple(sorted(d.iteritems())))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
4.7758948802947998
>>> timeit.timeit('hash(frozenset(d.iteritems()))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
1.8153600692749023

Перевага в продуктивності залежить від змісту словника, але в більшості випадків я тестував, хешування за допомогою frozensetпринаймні в 2 рази швидше (головним чином тому, що не потрібно сортувати).


1
Зверніть увагу, немає необхідності включати як ключі, так і значення. Це рішення було б набагато швидше , так як: hash(frozenset(d)).
Реймонд Хеттінгер

10
@RaymondHettinger: hash(frozenset(d))результати в однакових хешах для 2-х диктовок з однаковими клавішами, але різними значеннями!
Oben Sonne

4
Це не проблема. Завданням __eq__ є розмежування між різними значеннями. Робота __hash__ полягає лише у зменшенні простору пошуку.
Реймонд Хеттінгер

5
Це справедливо для теоретичної концепції хешів та зіставлення, але не практично для кеш-пам’яті зі словниками, як пошук - не рідкість, що словники з подібними ключами, але різними значеннями передаються в функцію кешування в пам’ять. У цьому випадку кеш практично перетворюється на список замість відображення, якщо для побудови хешу використовуються лише ключі.
Oben Sonne

3
У спеціальному випадку з набором значків з відступними клавішами та різними значеннями, вам було б краще просто зберігати хеш на основі frozenset(d.itervalues()). У тих випадках, коли дикти мають різні клавіші, frozenset(d)це набагато швидше і не накладає жодних обмежень на розширюваність ключів. Нарешті, пам’ятайте, що метод dict .__ eq__ перевірятиме рівність пар ключ / значення набагато швидше, ніж будь-що може обчислити хеш для всіх кортежів пари ключ / значення. Використання кортежів ключ / значення також проблематично, оскільки воно викидає збережені хеші для всіх ключів (саме тому frozenset(d)це так швидко).
Реймонд Хеттінгер,

11

Досить чистою, простою реалізацією є

import collections

class FrozenDict(collections.Mapping):
    """Don't forget the docstrings!!"""

    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)

    def __iter__(self):
        return iter(self._d)

    def __len__(self):
        return len(self._d)

    def __getitem__(self, key):
        return self._d[key]

    def __hash__(self):
        return hash(tuple(sorted(self._d.iteritems())))

Чому це так розумно, чисто і просто? Тобто поясніть, будь ласка, відмінності в інших відповідях, наприклад, необхідність __iter__та __len__.
Карл Ріхтер

1
@KarlRichter, я ніколи не говорив, що це було розумно, просто досить чисто. ;)
Майк Грем

@KarlRichter, я визначаю __iter__і __len__тому, що я повинен, оскільки я вивожу collections.Mapping; про те, як користуватися collections.Mapping, досить добре висвітлено в документації до модуля колекцій. Інші люди не відчувають потреби, оскільки вони виводять dict. Виходити dictз будь-якої іншої причини, крім як визначити, __missing__є поганою ідеєю. У специфікації dict не сказано, як діє dict в такому випадку, і насправді це в кінцевому підсумку матиме безліч невіртуальних методів, які є менш корисними в цілому, і в цьому конкретному випадку будуть мати рудиментарні методи з неактуальною поведінкою.
Mike Graham

7

Я постійно повертаюся до цієї теми ... Ось ще одна варіація. Мені неприємно підкласифікуватиdict щоб додати __hash__метод; Практично не врятуватися від проблеми, яку диктофон міняє, і довіряти, що вони не зміняться, здається слабкою ідеєю. Отже, я натомість розглянув побудову відображення на основі вбудованого типу, який сам по собі є незмінним. хочаtuple це очевидний вибір, доступ до значень у ньому передбачає сортування та бісектрису; не проблема, але, схоже, це не використовує більшу частину потужності типу, на якому він побудований.

Що робити, якщо ви заклинили ключ, значення пар в frozenset ? Що б це вимагало, як би це працювало?

Частина 1, вам потрібен спосіб кодування елементів, таким чином, щоб заморожений набір обробляв їх переважно за їх ключами; Я зроблю для цього невеликий підклас.

import collections
class pair(collections.namedtuple('pair_base', 'key value')):
    def __hash__(self):
        return hash((self.key, None))
    def __eq__(self, other):
        if type(self) != type(other):
            return NotImplemented
        return self.key == other.key
    def __repr__(self):
        return repr((self.key, self.value))

Це одне ставить вас на відстань від незмінного відображення:

>>> frozenset(pair(k, v) for k, v in enumerate('abcd'))
frozenset([(0, 'a'), (2, 'c'), (1, 'b'), (3, 'd')])
>>> pairs = frozenset(pair(k, v) for k, v in enumerate('abcd'))
>>> pair(2, None) in pairs
True
>>> pair(5, None) in pairs
False
>>> goal = frozenset((pair(2, None),))
>>> pairs & goal
frozenset([(2, None)])

Ой! На жаль, коли ви використовуєте оператори множини, і елементи є рівними, але не однаковим об'єктом; яке з них закінчується повернутим значенням не визначено , нам доведеться пройти ще кілька обертань.

>>> pairs - (pairs - goal)
frozenset([(2, 'c')])
>>> iter(pairs - (pairs - goal)).next().value
'c'

Однак шукати значення таким чином громіздко, і що ще гірше, створює багато проміжних наборів; це не допоможе! Ми створимо пару `` підробка '' ключ-значення, щоб обійти її:

class Thief(object):
    def __init__(self, key):
        self.key = key
    def __hash__(self):
        return hash(pair(self.key, None))
    def __eq__(self, other):
        self.value = other.value
        return pair(self.key, None) == other

Що призводить до менш проблемних:

>>> thief = Thief(2)
>>> thief in pairs
True
>>> thief.value
'c'

Це вся глибока магія; решта обертає все це у щось, що має такий інтерфейс, як дикт. Оскільки ми підкласуємо з frozenset, який має зовсім інший інтерфейс, існує досить багато методів; ми отримуємо невелику допомогу collections.Mapping, але більша частина роботи замінює frozensetметоди для версій, які працюють як дикти, замість цього:

class FrozenDict(frozenset, collections.Mapping):
    def __new__(cls, seq=()):
        return frozenset.__new__(cls, (pair(k, v) for k, v in seq))
    def __getitem__(self, key):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        raise KeyError(key)
    def __eq__(self, other):
        if not isinstance(other, FrozenDict):
            return dict(self.iteritems()) == other
        if len(self) != len(other):
            return False
        for key, value in self.iteritems():
            try:
                if value != other[key]:
                    return False
            except KeyError:
                return False
        return True
    def __hash__(self):
        return hash(frozenset(self.iteritems()))
    def get(self, key, default=None):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        return default
    def __iter__(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def iteritems(self):
        for item in frozenset.__iter__(self):
            yield (item.key, item.value)
    def iterkeys(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def itervalues(self):
        for item in frozenset.__iter__(self):
            yield item.value
    def __contains__(self, key):
        return frozenset.__contains__(self, pair(key, None))
    has_key = __contains__
    def __repr__(self):
        return type(self).__name__ + (', '.join(repr(item) for item in self.iteritems())).join('()')
    @classmethod
    def fromkeys(cls, keys, value=None):
        return cls((key, value) for key in keys)

що, зрештою, відповідає на моє власне запитання:

>>> myDict = {}
>>> myDict[FrozenDict(enumerate('ab'))] = 5
>>> FrozenDict(enumerate('ab')) in myDict
True
>>> FrozenDict(enumerate('bc')) in myDict
False
>>> FrozenDict(enumerate('ab', 3)) in myDict
False
>>> myDict[FrozenDict(enumerate('ab'))]
5

5

Прийнята відповідь @Unknown, а також відповідь @AlexMartelli працюють абсолютно нормально, але лише за таких обмежень:

  1. Значення словника мають бути хешируемыми. Наприклад, hash(hashabledict({'a':[1,2]}))підніме TypeError.
  2. Ключі повинні підтримувати операцію порівняння. Наприклад,hash(hashabledict({'a':'a', 1:1})) піднімеTypeError .
  3. Оператор порівняння ключів накладає повне впорядкування. Наприклад, якщо дві клавіші у словнику є frozenset((1,2,3))і frozenset((4,5,6)), вони порівнюють неоднакові в обох напрямках. Отже, сортування елементів словника за допомогою таких ключів може призвести до довільного порядку, а отже, буде порушено правило, згідно з яким рівні об’єкти повинні мати однакове хеш-значення.

Набагато швидша відповідь @ObenSonne знімає обмеження 2 і 3, але все ще обмежується обмеженням 1 (значення повинні бути хешируемими).

Швидша, але відповідь @RaymondHettinger знімає всі 3 обмеження, оскільки вона не включає .values()в обчислення хешу. Однак його ефективність хороша лише в тому випадку, якщо:

  1. Більшість (нерівних) словників, які потрібно хешувати, не ідентичні .keys() .

Якщо ця умова не виконується, хеш-функція все ще буде дійсною, але може спричинити забагато колізій. Наприклад, у крайньому випадку, коли всі словники генеруються із шаблону веб-сайту (імена полів як ключі, введення користувачем як значення), ключі завжди будуть однаковими, а хеш-функція поверне одне і те ж значення для всіх входів . Як результат, хеш-таблиця, яка покладається на таку хеш-функцію, стане такою ж повільною, як і список при отриманні елемента ( O(N)замістьO(1) ).

Я думаю, що наступне рішення буде працювати досить добре, навіть якщо всі 4 обмеження, які я перелічив вище, порушені. Це має додаткову перевагу в тому, що він може хешувати не лише словники, але й будь-які контейнери, навіть якщо вони мають вкладені змінні контейнери.

Я був би дуже вдячний за будь-які відгуки щодо цього, оскільки поки що я протестував це лише незначно.

# python 3.4
import collections
import operator
import sys
import itertools
import reprlib

# a wrapper to make an object hashable, while preserving equality
class AutoHash:
    # for each known container type, we can optionally provide a tuple
    # specifying: type, transform, aggregator
    # even immutable types need to be included, since their items
    # may make them unhashable

    # transformation may be used to enforce the desired iteration
    # the result of a transformation must be an iterable
    # default: no change; for dictionaries, we use .items() to see values

    # usually transformation choice only affects efficiency, not correctness

    # aggregator is the function that combines all items into one object
    # default: frozenset; for ordered containers, we can use tuple

    # aggregator choice affects both efficiency and correctness
    # e.g., using a tuple aggregator for a set is incorrect,
    # since identical sets may end up with different hash values
    # frozenset is safe since at worst it just causes more collisions
    # unfortunately, no collections.ABC class is available that helps
    # distinguish ordered from unordered containers
    # so we need to just list them out manually as needed

    type_info = collections.namedtuple(
        'type_info',
        'type transformation aggregator')

    ident = lambda x: x
    # order matters; first match is used to handle a datatype
    known_types = (
        # dict also handles defaultdict
        type_info(dict, lambda d: d.items(), frozenset), 
        # no need to include set and frozenset, since they are fine with defaults
        type_info(collections.OrderedDict, ident, tuple),
        type_info(list, ident, tuple),
        type_info(tuple, ident, tuple),
        type_info(collections.deque, ident, tuple),
        type_info(collections.Iterable, ident, frozenset) # other iterables
    )

    # hash_func can be set to replace the built-in hash function
    # cache can be turned on; if it is, cycles will be detected,
    # otherwise cycles in a data structure will cause failure
    def __init__(self, data, hash_func=hash, cache=False, verbose=False):
        self._data=data
        self.hash_func=hash_func
        self.verbose=verbose
        self.cache=cache
        # cache objects' hashes for performance and to deal with cycles
        if self.cache:
            self.seen={}

    def hash_ex(self, o):
        # note: isinstance(o, Hashable) won't check inner types
        try:
            if self.verbose:
                print(type(o),
                    reprlib.repr(o),
                    self.hash_func(o),
                    file=sys.stderr)
            return self.hash_func(o)
        except TypeError:
            pass

        # we let built-in hash decide if the hash value is worth caching
        # so we don't cache the built-in hash results
        if self.cache and id(o) in self.seen:
            return self.seen[id(o)][0] # found in cache

        # check if o can be handled by decomposing it into components
        for typ, transformation, aggregator in AutoHash.known_types:
            if isinstance(o, typ):
                # another option is:
                # result = reduce(operator.xor, map(_hash_ex, handler(o)))
                # but collisions are more likely with xor than with frozenset
                # e.g. hash_ex([1,2,3,4])==0 with xor

                try:
                    # try to frozenset the actual components, it's faster
                    h = self.hash_func(aggregator(transformation(o)))
                except TypeError:
                    # components not hashable with built-in;
                    # apply our extended hash function to them
                    h = self.hash_func(aggregator(map(self.hash_ex, transformation(o))))
                if self.cache:
                    # storing the object too, otherwise memory location will be reused
                    self.seen[id(o)] = (h, o)
                if self.verbose:
                    print(type(o), reprlib.repr(o), h, file=sys.stderr)
                return h

        raise TypeError('Object {} of type {} not hashable'.format(repr(o), type(o)))

    def __hash__(self):
        return self.hash_ex(self._data)

    def __eq__(self, other):
        # short circuit to save time
        if self is other:
            return True

        # 1) type(self) a proper subclass of type(other) => self.__eq__ will be called first
        # 2) any other situation => lhs.__eq__ will be called first

        # case 1. one side is a subclass of the other, and AutoHash.__eq__ is not overridden in either
        # => the subclass instance's __eq__ is called first, and we should compare self._data and other._data
        # case 2. neither side is a subclass of the other; self is lhs
        # => we can't compare to another type; we should let the other side decide what to do, return NotImplemented
        # case 3. neither side is a subclass of the other; self is rhs
        # => we can't compare to another type, and the other side already tried and failed;
        # we should return False, but NotImplemented will have the same effect
        # any other case: we won't reach the __eq__ code in this class, no need to worry about it

        if isinstance(self, type(other)): # identifies case 1
            return self._data == other._data
        else: # identifies cases 2 and 3
            return NotImplemented

d1 = {'a':[1,2], 2:{3:4}}
print(hash(AutoHash(d1, cache=True, verbose=True)))

d = AutoHash(dict(a=1, b=2, c=3, d=[4,5,6,7], e='a string of chars'),cache=True, verbose=True)
print(hash(d))

2

Можливо, ви також захочете додати ці два методи, щоб змусити роботу протоколу засолу v2 працювати з екземплярами хешдикту. В іншому випадку cPickle спробує використати хешдикт .____ setitem____, що призведе до помилки TypeError. Цікаво, що з двома іншими версіями протоколу ваш код чудово працює.

def __setstate__(self, objstate):
    for k,v in objstate.items():
        dict.__setitem__(self,k,v)
def __reduce__(self):
    return (hashdict, (), dict(self),)

-2

Якщо ви не вводите числа у словник і ніколи не втрачаєте змінних, що містять ваші словники, ви можете зробити це:

cache[id(rule)] = "whatever"

оскільки id () унікальний для кожного словника

РЕДАГУВАТИ:

О, вибачте, так, у такому випадку те, що сказали інші хлопці, було б краще. Я думаю, ви також можете серіалізувати свої словники як рядок, наприклад

cache[ 'foo:bar' ] = 'baz'

Якщо вам потрібно відновити словники з клавіш, тоді вам доведеться зробити щось потворніше

cache[ 'foo:bar' ] = ( {'foo':'bar'}, 'baz' )

Думаю, перевага цього полягає в тому, що вам не доведеться писати стільки коду.


Хм, ні; це не те, що я шукаю:. cache[id({'foo':'bar'})] = 'baz'; id({'foo':'bar'}) not in cacheМожливість динамічного створення ключів має важливе значення, коли я, насамперед, хочу використовувати дикти як ключі.
SingleNegationElimination

1
Серіалізація диктовок може бути нормальною, чи є у вас рекомендація щодо серіалізації? це те, що я шукаю.
SingleNegationElimination
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.