Что такое "замороженный дикт"?
- Замороженный набор - это фрозенет.
- Замороженный список может быть кортежем.
- Каким будет замороженный дикт? Неизменный, бескомпромиссный диктат.
Я думаю, что это может быть что-то вроде collections.namedtuple
, но это больше похоже на диктат замороженных клавиш (полузамороженный диктат). Не так ли?
"Frozendict" должен быть замороженным словарем, он должен иметь keys
, values
, get
и т.д., А также поддерживать in
, for
и т.д.
обновление:
* вот оно: https://www.python.org/dev/peps/pep-0603
Ответы
Ответ 1
Python не имеет встроенного типа frozendict. Оказывается, это было бы не слишком полезно (хотя, вероятно, это было бы полезно чаще, чем frozenset
).
Наиболее распространенной причиной такого типа является то, что функция memoizing вызывает функции с неизвестными аргументами. Наиболее распространенным решением для хранения хэшируемого эквивалента dict (где значения хешируются) является нечто вроде tuple(sorted(kwargs.iteritems()))
.
Это зависит от того, что сортировка не является безумной. Python не может положительно пообещать, что сортировка приведет к чему-то разумному здесь. (Но он не может обещать больше, так что не переживайте слишком много.)
Вы могли бы легко создать какую-то оболочку, которая работает так же, как и dict. Это может выглядеть примерно так:
import collections
class FrozenDict(collections.Mapping):
"""Don't forget the docstrings!!"""
def __init__(self, *args, **kwargs):
self._d = dict(*args, **kwargs)
self._hash = None
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):
# It would have been simpler and maybe more obvious to
# use hash(tuple(sorted(self._d.iteritems()))) from this discussion
# so far, but this solution is O(n). I don't know what kind of
# n we are going to run into, but sometimes it hard to resist the
# urge to optimize when it will gain improved algorithmic performance.
if self._hash is None:
self._hash = 0
for pair in self.iteritems():
self._hash ^= hash(pair)
return self._hash
Он должен отлично работать:
>>> x = FrozenDict(a=1, b=2)
>>> y = FrozenDict(a=1, b=2)
>>> x is y
False
>>> x == y
True
>>> x == {'a': 1, 'b': 2}
True
>>> d = {x: 'foo'}
>>> d[y]
'foo'
Ответ 2
Любопытно, что, хотя у нас редко встречается frozenset
в python, застывшего отображения все еще нет. Идея была отвергнута в PEP 416 - добавьте встроенный тип frozendict. Идея может быть пересмотрена в Python 3.9, см. PEP 603. Добавление типа замороженной карты в коллекции.
Итак, решение Python 2 для этого:
def foo(config={'a': 1}):
...
По-прежнему выглядит несколько отстойно:
def foo(config=None):
if config is None:
config = default_config = {'a': 1}
...
В python3 у вас есть возможность этого:
from types import MappingProxyType
default_config = {'a': 1}
DEFAULTS = MappingProxyType(default_config)
def foo(config=DEFAULTS):
...
Теперь стандартная конфигурация может обновляться динамически, но оставаться неизменной там, где вы хотите, чтобы она была неизменной, вместо этого передавая прокси.
Таким образом, изменения в default_config
обновят DEFAULTS
, как и ожидалось, но вы не можете записать в сам объект сопоставления прокси.
По общему признанию, это не совсем то же самое, что "неизменный, легко изменяемый диктат", - но это достойный заменитель, если использовать те же варианты использования, для которых мы могли бы захотеть замерзнуть.
Ответ 3
Предполагая, что ключи и значения словаря сами неизменяемы (например, строки), тогда:
>>> d
{'forever': 'atones', 'minks': 'cards', 'overhands': 'warranted',
'hardhearted': 'tartly', 'gradations': 'snorkeled'}
>>> t = tuple((k, d[k]) for k in sorted(d.keys()))
>>> hash(t)
1524953596
Ответ 4
Здесь нет fronzedict
, но вы можете использовать MappingProxyType
который был добавлен в стандартную библиотеку с Python 3.3:
>>> from types import MappingProxyType
>>> foo = MappingProxyType({'a': 1})
>>> foo
mappingproxy({'a': 1})
>>> foo['a'] = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> foo
mappingproxy({'a': 1})
Ответ 5
Вот код, который я использовал. Я подклассифицировал frozenset. Преимущества этого заключаются в следующем.
- Это действительно неизменный объект. Не полагайтесь на хорошее поведение будущих пользователей и разработчиков.
- Легко конвертировать назад и вперед между обычным словарем и замороженным словарем. FrozenDict (orig_dict) → замороженный словарь. dict (frozen_dict) → regular dict.
Обновление 21 января 2015 г. В исходном фрагменте кода, который я опубликовал в 2014 году, использовался цикл for, чтобы найти соответствующий ключ. Это было невероятно медленно. Теперь я собрал реализацию, которая использует функции хеширования frozenset. Ключи-значения хранятся в специальных контейнерах, где функции __hash__
и __eq__
основаны только на ключе. Этот код также был официально протестирован в модуле, в отличие от того, что я опубликовал здесь в августе 2014 года.
Лицензия типа MIT.
if 3 / 2 == 1:
version = 2
elif 3 / 2 == 1.5:
version = 3
def col(i):
''' For binding named attributes to spots inside subclasses of tuple.'''
g = tuple.__getitem__
@property
def _col(self):
return g(self,i)
return _col
class Item(tuple):
''' Designed for storing key-value pairs inside
a FrozenDict, which itself is a subclass of frozenset.
The __hash__ is overloaded to return the hash of only the key.
__eq__ is overloaded so that normally it only checks whether the Item's
key is equal to the other object, HOWEVER, if the other object itself
is an instance of Item, it checks BOTH the key and value for equality.
WARNING: Do not use this class for any purpose other than to contain
key value pairs inside FrozenDict!!!!
The __eq__ operator is overloaded in such a way that it violates a
fundamental property of mathematics. That property, which says that
a == b and b == c implies a == c, does not hold for this object.
Here a demonstration:
[in] >>> x = Item(('a',4))
[in] >>> y = Item(('a',5))
[in] >>> hash('a')
[out] >>> 194817700
[in] >>> hash(x)
[out] >>> 194817700
[in] >>> hash(y)
[out] >>> 194817700
[in] >>> 'a' == x
[out] >>> True
[in] >>> 'a' == y
[out] >>> True
[in] >>> x == y
[out] >>> False
'''
__slots__ = ()
key, value = col(0), col(1)
def __hash__(self):
return hash(self.key)
def __eq__(self, other):
if isinstance(other, Item):
return tuple.__eq__(self, other)
return self.key == other
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return '%r: %r' % self
def __repr__(self):
return 'Item((%r, %r))' % self
class FrozenDict(frozenset):
''' Behaves in most ways like a regular dictionary, except that it immutable.
It differs from other implementations because it doesn't subclass "dict".
Instead it subclasses "frozenset" which guarantees immutability.
FrozenDict instances are created with the same arguments used to initialize
regular dictionaries, and has all the same methods.
[in] >>> f = FrozenDict(x=3,y=4,z=5)
[in] >>> f['x']
[out] >>> 3
[in] >>> f['a'] = 0
[out] >>> TypeError: 'FrozenDict' object does not support item assignment
FrozenDict can accept un-hashable values, but FrozenDict is only hashable if its values are hashable.
[in] >>> f = FrozenDict(x=3,y=4,z=5)
[in] >>> hash(f)
[out] >>> 646626455
[in] >>> g = FrozenDict(x=3,y=4,z=[])
[in] >>> hash(g)
[out] >>> TypeError: unhashable type: 'list'
FrozenDict interacts with dictionary objects as though it were a dict itself.
[in] >>> original = dict(x=3,y=4,z=5)
[in] >>> frozen = FrozenDict(x=3,y=4,z=5)
[in] >>> original == frozen
[out] >>> True
FrozenDict supports bi-directional conversions with regular dictionaries.
[in] >>> original = {'x': 3, 'y': 4, 'z': 5}
[in] >>> FrozenDict(original)
[out] >>> FrozenDict({'x': 3, 'y': 4, 'z': 5})
[in] >>> dict(FrozenDict(original))
[out] >>> {'x': 3, 'y': 4, 'z': 5} '''
__slots__ = ()
def __new__(cls, orig={}, **kw):
if kw:
d = dict(orig, **kw)
items = map(Item, d.items())
else:
try:
items = map(Item, orig.items())
except AttributeError:
items = map(Item, orig)
return frozenset.__new__(cls, items)
def __repr__(self):
cls = self.__class__.__name__
items = frozenset.__iter__(self)
_repr = ', '.join(map(str,items))
return '%s({%s})' % (cls, _repr)
def __getitem__(self, key):
if key not in self:
raise KeyError(key)
diff = self.difference
item = diff(diff({key}))
key, value = set(item).pop()
return value
def get(self, key, default=None):
if key not in self:
return default
return self[key]
def __iter__(self):
items = frozenset.__iter__(self)
return map(lambda i: i.key, items)
def keys(self):
items = frozenset.__iter__(self)
return map(lambda i: i.key, items)
def values(self):
items = frozenset.__iter__(self)
return map(lambda i: i.value, items)
def items(self):
items = frozenset.__iter__(self)
return map(tuple, items)
def copy(self):
cls = self.__class__
items = frozenset.copy(self)
dupl = frozenset.__new__(cls, items)
return dupl
@classmethod
def fromkeys(cls, keys, value):
d = dict.fromkeys(keys,value)
return cls(d)
def __hash__(self):
kv = tuple.__hash__
items = frozenset.__iter__(self)
return hash(frozenset(map(kv, items)))
def __eq__(self, other):
if not isinstance(other, FrozenDict):
try:
other = FrozenDict(other)
except Exception:
return False
return frozenset.__eq__(self, other)
def __ne__(self, other):
return not self.__eq__(other)
if version == 2:
#Here are the Python2 modifications
class Python2(FrozenDict):
def __iter__(self):
items = frozenset.__iter__(self)
for i in items:
yield i.key
def iterkeys(self):
items = frozenset.__iter__(self)
for i in items:
yield i.key
def itervalues(self):
items = frozenset.__iter__(self)
for i in items:
yield i.value
def iteritems(self):
items = frozenset.__iter__(self)
for i in items:
yield (i.key, i.value)
def has_key(self, key):
return key in self
def viewkeys(self):
return dict(self).viewkeys()
def viewvalues(self):
return dict(self).viewvalues()
def viewitems(self):
return dict(self).viewitems()
#If this is Python2, rebuild the class
#from scratch rather than use a subclass
py3 = FrozenDict.__dict__
py3 = {k: py3[k] for k in py3}
py2 = {}
py2.update(py3)
dct = Python2.__dict__
py2.update({k: dct[k] for k in dct})
FrozenDict = type('FrozenDict', (frozenset,), py2)
Ответ 6
Я думаю о frozendict каждый раз, когда я пишу такую функцию:
def do_something(blah, optional_dict_parm=None):
if optional_dict_parm is None:
optional_dict_parm = {}
Ответ 7
Вы можете использовать frozendict
из utilspie
как:
>>> from utilspie.collectionsutils import frozendict
>>> my_dict = frozendict({1: 3, 4: 5})
>>> my_dict # object of `frozendict` type
frozendict({1: 3, 4: 5})
# Hashable
>>> {my_dict: 4}
{frozendict({1: 3, 4: 5}): 4}
# Immutable
>>> my_dict[1] = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/mquadri/workspace/utilspie/utilspie/collectionsutils/collections_utils.py", line 44, in __setitem__
self.__setitem__.__name__, type(self).__name__))
AttributeError: You can not call '__setitem__()' for 'frozendict' object
В соответствии с :
frozendict (dict_obj): принимает obj типа dict и возвращает хешируемый и неизменный dict
Ответ 8
Да, это мой второй ответ, но это совершенно другой подход. Первая реализация была в чистом питоне. Этот находится в Китоне. Если вы знаете, как использовать и компилировать модули Cython, это так же быстро, как и обычный словарь. Пример: от 0,04 до 0,6 микросекунды для получения одного значения.
Это файл "frozen_dict.pyx"
import cython
from collections import Mapping
cdef class dict_wrapper:
cdef object d
cdef int h
def __init__(self, *args, **kw):
self.d = dict(*args, **kw)
self.h = -1
def __len__(self):
return len(self.d)
def __iter__(self):
return iter(self.d)
def __getitem__(self, key):
return self.d[key]
def __hash__(self):
if self.h == -1:
self.h = hash(frozenset(self.d.iteritems()))
return self.h
class FrozenDict(dict_wrapper, Mapping):
def __repr__(self):
c = type(self).__name__
r = ', '.join('%r: %r' % (k,self[k]) for k in self)
return '%s({%s})' % (c, r)
__all__ = ['FrozenDict']
Здесь файл "setup.py"
from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize('frozen_dict.pyx')
)
Если у вас установлен Cython, сохраните два файла выше в одном каталоге. Перейдите в этот каталог в командной строке.
python setup.py build_ext --inplace
python setup.py install
И вы должны это сделать.
Ответ 9
Основным недостатком namedtuple
является то, что он должен быть указан до его использования, поэтому он менее удобен для одноразовых случаев.
Однако существует практическое обходное решение, которое можно использовать для обработки многих таких случаев. Скажем, что вы хотите иметь неизменный эквивалент следующего dict:
MY_CONSTANT = {
'something': 123,
'something_else': 456
}
Эту функцию можно эмулировать следующим образом:
from collections import namedtuple
MY_CONSTANT = namedtuple('MyConstant', 'something something_else')(123, 456)
Можно даже написать вспомогательную функцию для автоматизации этого:
def freeze_dict(data):
from collections import namedtuple
keys = sorted(data.keys())
frozen_type = namedtuple(''.join(keys), keys)
return frozen_type(**data)
a = {'foo':'bar', 'x':'y'}
fa = freeze_dict(data)
assert a['foo'] == fa.foo
Конечно, это работает только для плоских dicts, но не слишком сложно реализовать рекурсивную версию.
Ответ 10
Установить Frozendict
pip install frozendict
Используй это!
from frozendict import frozendict
def smth(param = frozendict({})):
pass
Ответ 11
Другим вариантом является класс MultiDictProxy
из пакета multidict
.
Ответ 12
Кажется, что почти тривиальное решение, которое другие ответы пропустили. Самый тривиальный ответ должен быть чем-то вроде
x = {'a': 1, 'b': 2}
x.__setitem__ = None
за исключением того, что __setitem__
является атрибутом только для чтения в dict
, поэтому мы должны его переопределить:
class FrozenDict(dict):
def __setitem__(self, key, value):
raise NotImplementedError('Invalid operation')
Вы можете добавить метод __hash__
к вашему классу так же, как подсказывает @MikeGraham.
Вы можете сделать класс еще более неизменным, если возникают ошибки __setattr__
и __setattribute__
, особенно последние. Кроме того, вы можете определить пустой класс __slots__
в классе. Однако ничто из этого не является строго необходимым, поскольку пользовательские атрибуты не влияют на результат ==
:
>>> y = {'a': 1, 'b': 2}
>>> x = FrozenDict(y)
>>> x is y
False
>>> x == y
True
>>> x.random = 5
>>> x == y
True
>>> y['c'] = 3
>>> x == y
False
Еще одна вещь, о которой нужно помнить, - это то, что вы всегда можете изменить определенный таким образом dict, если вы готовы перепрыгнуть через обручи:
dict.__setitem__(x, 'c', 3)
обойдет мерзлоту. Независимо от того, является ли это ошибкой или функцией, вы должны решить.
Ответ 13
Подкласс dict
Я вижу этот образец в дикой природе (github) и хотел бы упомянуть его:
class FrozenDict(dict):
def __init__(self, *args, **kwargs):
self._hash = None
super(FrozenDict, self).__init__(*args, **kwargs)
def __hash__(self):
if self._hash is None:
self._hash = hash(tuple(sorted(self.items()))) # iteritems() on py2
return self._hash
def _immutable(self, *args, **kws):
raise TypeError('cannot change object - object is immutable')
__setitem__ = _immutable
__delitem__ = _immutable
pop = _immutable
popitem = _immutable
clear = _immutable
update = _immutable
setdefault = _immutable
пример использования:
d1 = FrozenDict({'a': 1, 'b': 2})
d2 = FrozenDict({'a': 1, 'b': 2})
d1.keys()
assert isinstance(d1, dict)
assert len(set([d1, d2])) == 1 # hashable
Pros
- поддержка
get()
, keys()
, items()
(iteritems()
на py2) и все плюсы от dict
из ящика без явного их реализации
- использует внутренне
dict
, что означает, что производительность (dict
написана на c в CPython)
- элегантный простой и без черной магии
-
isinstance(my_frozen_dict, dict)
возвращает True - хотя python поощряет duck-typing многие пакеты используют isinstance()
, это может сэкономить много настроек и настроек
против
- любой подкласс может переопределить это или получить доступ к нему изнутри (вы не можете на самом деле 100% защитить что-то на python, вы должны доверять своим пользователям и предоставлять хорошую документацию).
- Если вам нужна скорость, вы можете сделать
__hash__
немного быстрее.
Ответ 14
В отсутствие поддержки на родном языке вы можете сделать это самостоятельно или использовать существующее решение. К счастью, Python упрощает продление базовых реализаций.
class frozen_dict(dict):
def __setitem__(self, key, value):
raise Exception('Frozen dictionaries cannot be mutated')
frozen_dict = frozen_dict({'foo': 'FOO' })
print(frozen['foo']) # FOO
frozen['foo'] = 'NEWFOO' # Exception: Frozen dictionaries cannot be mutated
# OR
from types import MappingProxyType
frozen_dict = MappingProxyType({'foo': 'FOO'})
print(frozen_dict['foo']) # FOO
frozen_dict['foo'] = 'NEWFOO' # TypeError: 'mappingproxy' object does not support item assignment
Ответ 15
Мне нужно было получить доступ к фиксированным ключам для чего-то, что-то вроде глобально-постоянного типа вещей, и я остановился на чем-то вроде этого:
class MyFrozenDict:
def __getitem__(self, key):
if key == 'mykey1':
return 0
if key == 'mykey2':
return "another value"
raise KeyError(key)
Используйте это как
a = MyFrozenDict()
print(a['mykey1'])
ПРЕДУПРЕЖДЕНИЕ: я не рекомендую это для большинства случаев использования, поскольку это делает некоторые довольно серьезные компромиссы.