Диктовки, що розширюються 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.