Неизменяемый словарь, используйте только ключ для другого словаря
У меня возникла необходимость в реализации хэшируемого dict, чтобы я мог использовать словарь в качестве ключа для другого словаря.
Несколько месяцев назад я использовал эту реализацию: Python hashable dicts
Однако я получил известие от коллеги, сказав: "На самом деле это не является неизменным, поэтому это небезопасно. Вы можете использовать его, но это заставляет меня чувствовать себя грустным Panda.
Итак, я начал искать вокруг, чтобы создать неизменную. Мне не нужно сравнивать "ключ-dict" с другим "ключ-dict". Его единственное использование - это ключ для другого словаря.
Я придумал следующее:
class HashableDict(dict):
"""Hashable dict that can be used as a key in other dictionaries"""
def __new__(self, *args, **kwargs):
# create a new local dict, that will be used by the HashableDictBase closure class
immutableDict = dict(*args, **kwargs)
class HashableDictBase(object):
"""Hashable dict that can be used as a key in other dictionaries. This is now immutable"""
def __key(self):
"""Return a tuple of the current keys"""
return tuple((k, immutableDict[k]) for k in sorted(immutableDict))
def __hash__(self):
"""Return a hash of __key"""
return hash(self.__key())
def __eq__(self, other):
"""Compare two __keys"""
return self.__key() == other.__key() # pylint: disable-msg=W0212
def __repr__(self):
"""@see: dict.__repr__"""
return immutableDict.__repr__()
def __str__(self):
"""@see: dict.__str__"""
return immutableDict.__str__()
def __setattr__(self, *args):
raise TypeError("can't modify immutable instance")
__delattr__ = __setattr__
return HashableDictBase()
Для проверки функциональности я использовал следующее:
d = {"a" : 1}
a = HashableDict(d)
b = HashableDict({"b" : 2})
print a
d["b"] = 2
print a
c = HashableDict({"a" : 1})
test = {a : "value with a dict as key (key a)",
b : "value with a dict as key (key b)"}
print test[a]
print test[b]
print test[c]
который дает:
{'a': 1}
{'a': 1}
значение с ключом dict (ключ a)
значение с ключом dict (ключ b)
значение с помощью ключа dict (клавиша a)
как вывод
Является ли это "наилучшим возможным" неизменным словарем, который я могу использовать, который удовлетворяет моим требованиям? Если нет, то что было бы лучшим решением?
Ответы
Ответ 1
Если вы используете его только как ключ для другого dict
, вы можете пойти на frozenset(mutabledict.items())
. Если вам нужно получить доступ к базовым сопоставлениям, вы можете использовать его как параметр dict
.
mutabledict = dict(zip('abc', range(3)))
immutable = frozenset(mutabledict.items())
read_frozen = dict(immutable)
read_frozen['a'] # => 1
Обратите внимание, что вы также можете объединить это с классом, производным от dict
, и использовать frozenset
в качестве источника хеша, отключив __setitem__
, как это предлагается в другом ответе. (@RaymondHettinger ответ для кода, который делает именно это).
Ответ 2
Абсолютный базовый класс Mapping упрощает его реализацию:
import collections
class ImmutableDict(collections.Mapping):
def __init__(self, somedict):
self._dict = dict(somedict) # make a copy
self._hash = None
def __getitem__(self, key):
return self._dict[key]
def __len__(self):
return len(self._dict)
def __iter__(self):
return iter(self._dict)
def __hash__(self):
if self._hash is None:
self._hash = hash(frozenset(self._dict.items()))
return self._hash
def __eq__(self, other):
return self._dict == other._dict
Ответ 3
Чтобы ваш неизменный словарь был безопасным, все, что ему нужно сделать, никогда не изменяет его хэш. Почему бы вам просто отключить __setitem__
следующим образом:
class ImmutableDict(dict):
def __setitem__(self, key, value):
raise Exception("Can't touch this")
def __hash__(self):
return hash(tuple(sorted(self.items())))
a = ImmutableDict({'a':1})
b = {a:1}
print b
print b[a]
a['a'] = 0
Выходной сигнал script:
{{'a': 1}: 1}
1
Traceback (most recent call last):
File "ex.py", line 11, in <module>
a['a'] = 0
File "ex.py", line 3, in __setitem__
raise Exception("Can't touch this")
Exception: Can't touch this
Ответ 4
Я понимаю, что это уже ответили, но types.MappingProxyType является аналогичной реализацией для Python 3.3. Что касается первоначального вопроса безопасности, то в разделе PEP 416 - добавьте встроенный тип frozendict, почему была отклонена идея frozendict
.
Ответ 5
Кажется, я опаздываю, чтобы опубликовать. Не уверен, что кто-то еще придумал идеи. Но вот мое занятие. Дикт является неизменным и хешируемым. Я сделал его неизменным, переопределив все методы, магию и прочее с помощью специальной функции "_readonly", которая вызывает исключение. Это делается при создании экземпляра объекта. Чтобы обойти проблему неспособности применить значения, я устанавливаю "хэш" под "__new__". Затем я переопределяю функцию __hash __. Это оно!
class ImmutableDict(dict):
_HASH = None
def __new__(cls, *args, **kwargs):
ImmutableDict._HASH = hash(frozenset(args[0].items()))
return super(ImmutableDict, cls).__new__(cls, args)
def __hash__(self):
return self._HASH
def _readonly(self, *args, **kwards):
raise TypeError("Cannot modify Immutable Instance")
__delattr__ = __setattr__ = __setitem__ = pop = update = setdefault = clear = popitem = _readonly
Тест:
immutabled1 = ImmutableDict ({ "This": "That", "Cheese": "Blarg" })
dict1 = {immutabled1: "Yay" }
dict1 [immutabled1]
"Yay"
dict1
{{'Cheese': 'Blarg', 'This': 'That'}: 'Yay'}
Ответ 6
Вот ссылка на pip install
-возможную реализацию ответа @RaymondHettinger: https://github.com/pcattori/icicle
Просто pip install icicle
, и вы можете from icicle import FrozenDict
!
Ответ 7
Изменение Ответ Раймонда Хеттингера, обернув self._dict
types.MappingProxyType
.
class ImmutableDict(collections.Mapping):
"""
Copies a dict and proxies it via types.MappingProxyType to make it immutable.
"""
def __init__(self, somedict):
dictcopy = dict(somedict) # make a copy
self._dict = MappingProxyType(dictcopy) # lock it
self._hash = None
def __getitem__(self, key):
return self._dict[key]
def __len__(self):
return len(self._dict)
def __iter__(self):
return iter(self._dict)
def __hash__(self):
if self._hash is None:
self._hash = hash(frozenset(self._dict.items()))
return self._hash
def __eq__(self, other):
return self._dict == other._dict
def __repr__(self):
return str(self._dict)