冻结集就是冻结集。 冻结列表可以是元组。 冷冻字典会是什么?一个不可变的、可哈希的字典。

我猜它可能是collections.namedtuple之类的东西,但那更像是一个冻结的字典(一个半冻结的字典)。不是吗?

一个“frozendict”应该是一个冻结的字典,它应该有键,值,get等,并支持in, for等。

更新: 在这里:https://www.python.org/dev/peps/pep-0603


当前回答

是的,这是我的第二个答案,但这是一个完全不同的方法。第一个实现是用纯python实现的。这是Cython的。如果您知道如何使用和编译Cython模块,这与使用常规字典一样快。大约0.04到0.06微秒来检索一个值。

这是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

你应该做完了。

其他回答

没有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})

Python没有内置的frozendict类型。事实证明,这并不经常有用(尽管它仍然可能比frozenset更有用)。

需要这种类型的最常见原因是在记忆函数调用带有未知参数的函数时。存储dict(其中值是可哈希的)的可哈希等价对象的最常见解决方案是类似tuple(sorted(kwargs.items()))的东西。

这取决于排序是不是有点疯狂。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:
            hash_ = 0
            for pair in self.items():
                hash_ ^= hash(pair)
            self._hash = hash_
        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'

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

当然,这只适用于平面字典,但实现递归版本应该不会太难。

子类化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')

    # makes (deep)copy alot more efficient
    def __copy__(self):
        return self

    def __deepcopy__(self, memo=None):
        if memo is not None:
            memo[id(self)] = self
        return self

    __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() (py2上的iteritems())以及dict中的所有功能,而无需显式地实现它们 使用内部dict,这意味着性能(dict在CPython中是用c编写的) 优雅简单,没有黑魔法 isinstance(my_frozen_dict, dict)返回True -尽管python鼓励使用isinstance()对许多包进行duck-typing,但这可以节省许多调整和自定义

Cons

任何子类都可以覆盖它或在内部访问它(你不能真正100%保护python中的某些东西,你应该相信你的用户并提供良好的文档)。 如果你关心速度,你可能想让__hash__更快一点。

你可以使用utispie包装的冷冻液:

>>> 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):接受dict类型的obj并返回一个可哈希且不可变的dict