Что такое "замороженный диктант"?
- замороженный набор frozenset.
- замороженный список может быть кортежем.
- что такое замороженный диктант? Неизменный, хешируемый дикт.
я думаю, это может быть что-то вроде collections.namedtuple, но это больше похоже на замороженный дикт (полузамороженный дикт). Не так ли?
"frozendict" должен быть замороженным словарем, он должен иметь keys,values,get и т. д. и поддержка in,for и т. д.
14 ответов:
Python не имеет встроенного типа frozendict. Оказывается, это не было бы полезно слишком часто (хотя это все равно было бы полезно чаще, чем
frozensetесть).наиболее распространенная причина, по которой требуется такой тип, - это когда функция memoizing вызывает функции с неизвестными аргументами. Наиболее распространенным решением для хранения хешируемого эквивалента dict (где значения хешируются) является что-то вроде
tuple(sorted(kwargs.iteritems())).Это зависит от сортировки не немного безумный. Python не может положительно обещать, что сортировка приведет к чему-то разумному здесь. (Но это не может обещать много другого, так что не потейте слишком много.)
вы могли бы достаточно легко сделать какую-то оболочку, которая работает так же, как дикт. Это может выглядеть примерно так
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's 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'
любопытно, хотя у нас редко бывает полезным
frozensetв python все еще нет замороженного отображения. Идея была отвергнута в PEP 416.Итак, решение 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как и ожидалось, но вы не можете писать в сам объект прокси-сервера сопоставления.по общему признанию, это не совсем то же самое, что "неизменяемый, хешируемый дикт", но это достойная замена, учитывая те же самые случаи использования, для которых нам может понадобиться frozendict.
предполагая, что ключи и значения словаря сами по себе неизменяемы (например, строки), то:
>>> 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
вот код, который я использую. Я подкласс frozenset. Преимущества этого заключаются в следующем.
- это действительно неизменяемый объект. Не стоит полагаться на хорошее поведение будущих пользователей и разработчиков.
- легко конвертировать туда и обратно между обычным словарем и замороженным словарем. FrozenDict (orig_dict) --> замороженный словарь. дикт (frozen_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's 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's 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)
Я думаю о frozendict каждый раз, когда я пишу такую функцию:
def do_something(blah, optional_dict_parm=None): if optional_dict_parm is None: optional_dict_parm = {}
вы можете использовать
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
Да, это мой второй ответ, но это совсем другой подход. Первая реализация была в чистом Python. Этот находится в Китоне. Если вы знаете, как использовать и компилировать модули Cython, это так же быстро, как обычный словарь. Грубо.04 для .06 микросекунд для получения одного значения.
Это файл " frozen_dict.дарохранительница"
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и вы должны быть сделаны.
главный минус
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конечно, это работает только для плоских диктов, но это не должно быть слишком сложно реализовать рекурсивную версию.
установить frozendict
pip install frozendictиспользовать его!
from frozendict import frozendict def smth(param = frozendict({})): pass
другой вариант
MultiDictProxyклассmultidictпакета.
нет
fronzedictно вы можете использоватьMappingProxyType:>>> 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})
наследование
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плюсы
- поддержка
get(),keys(),items()(iteritems()на py2) и все вкусности отdict"из коробки" без явного их реализации- использует внутренне
dictчто означает производительность (dictпишется на c in CPython)- элегантный простой и без черной магии
isinstance(my_frozen_dict, dict)возвращает True-хотя python поощряет утка-типирование многие пакеты используетisinstance(), это может сэкономить много настроек и настроекминусы
- любой подкласс может переопределить это или получить доступ к нему внутри (вы не можете действительно 100% защитить что-то в python, вы должны доверять своим пользователям и предоставлять хорошие документация.)
- если вы заботитесь о скорости, вы захотите сделать
__hash__немного быстрее.
при отсутствии поддержки родного языка, вы можете сделать это самостоятельно или использовать существующее решение. К счастью, 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
мне нужно было получить доступ к фиксированным ключам для чего-то в какой-то момент для чего-то, что было своего рода глобально-постоянным видом вещей, и я остановился на чем-то вроде этого:
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'])предупреждение: я не рекомендую это для большинства случаев использования, как это делает некоторые довольно серьезные недостатки.
Comments