web-dev-qa-db-ja.com

「凍結辞書」とは何でしょうか?

  • 凍結セットは凍結セットです。
  • 固定リストはタプルである可能性があります。
  • 凍結された辞書はどうなりますか?不変でハッシュ可能な辞書。

collections.namedtupleのようなものかもしれませんが、それはより多くの凍結キー辞書(半凍結辞書)に似ています。そうじゃない?

「frozendict」はフリーズ辞書である必要があり、keysvaluesgetなどを持ち、inforなど.

136
dugres

Pythonには組み込みのfrozendict型がありません。これはあまり有用ではないことが判明しています(ただし、frozensetよりも頻繁に有用である可能性があります)。

このような型が必要な最も一般的な理由は、不明な引数を持つ関数の関数呼び出しをメモするときです。 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'
102
Mike Graham

不思議なことに、Pythonでfrozensetを使用することはめったにありませんが、まだマッピングが凍結されていません。このアイデアは PEP 416 で拒否されました。

だからpython 2これに対する解決策:

def foo(config={'a': 1}):
    ...

それでもやや不自由なようです:

def foo(config=None):
    if config is None:
        config = default_config = {'a': 1}
    ...

Python3には this のオプションがあります:

from types import MappingProxyType

default_config = {'a': 1}
DEFAULTS = MappingProxyType(default_config)

def foo(config=DEFAULTS):
    ...

これで、デフォルトの構成canが動的に更新されますが、代わりにプロキシを渡すことで不変にしたい場所で不変のままです。

したがって、default_configDEFAULTSを期待どおりに更新しますが、マッピングプロキシオブジェクト自体に書き込むことはできません。

確かに、「不変でハッシュ可能な辞書」とはまったく同じものではありませんが、frozendictが必要になる可能性のある同じ種類のユースケースを考えると、それはまともな代替品です。

51
wim

辞書のキーと値自体が不変(たとえば文字列)であると仮定すると:

>>> 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
18
msw

fronzedictはありませんが、Python 3.3で標準ライブラリに追加された 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})
11
Julio Marins

これが私が使ってきたコードです。 frozensetをサブクラス化しました。この利点は次のとおりです。

  1. これは本当に不変のオブジェクトです。将来のユーザーと開発者の良い振る舞いに依存しません。
  2. 通常の辞書と固定辞書との間で簡単に変換できます。 FrozenDict(orig_dict)->凍結辞書。 dict(frozen_dict)->通常のdict。

2015年1月21日更新:2014年に投稿した元のコードは、forループを使用して一致するキーを見つけました。それは信じられないほど遅かった。ここで、frozensetのハッシュ機能を利用する実装をまとめました。キーと値のペアは、__hash__および__eq__関数はキーのみに基づいています。このコードは、2014年8月にここに投稿したものとは異なり、正式に単体テストされています。

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)
9
Steve Zelaznik

次のような関数を作成するたびに、frozendictを考えます。

def do_something(blah, optional_dict_parm=None):
    if optional_dict_parm is None:
        optional_dict_parm = {}
5
Mark Visser

frozendict from 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):dictタイプのobjを受け入れ、ハッシュ可能および不変のdictを返します

4

はい、これは私の2番目の答えですが、まったく別のアプローチです。最初の実装は純粋なpythonでした。これはCythonにあります。 Cythonモジュールの使用方法とコンパイル方法を知っている場合、これは通常の辞書と同じくらい高速です。単一の値を取得するには、おおよそ0.04〜.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がインストールされている場合、上記の2つのファイルを同じディレクトリに保存します。コマンドラインでそのディレクトリに移動します。

python setup.py build_ext --inplace
python setup.py install

これで完了です。

3
Steve Zelaznik

namedtupleの主な欠点は、使用する前に指定する必要があるため、使い捨ての場合にはあまり便利ではないことです。

ただし、このような多くのケースを処理するために使用できる実用的な回避策があります。次の辞書の不変の同等物を持ちたいとしましょう:

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

もちろん、これはフラットなディクテーションに対してのみ機能しますが、再帰バージョンを実装するのはそれほど難しくないはずです。

3
Berislav Lopac

インストール frozendict

pip install frozendict

これを使って!

from frozendict import frozendict

def smth(param = frozendict({})):
    pass
2
Andrey Korchak

別のオプションは MultiDictProxy パッケージのmultidictクラスです。

1
Berislav Lopac

ネイティブ言語のサポートがない場合は、自分で行うか、既存のソリューションを使用できます。幸いなことに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
0
efreezy

ある時点で何かの固定キーにアクセスする必要がありましたが、それはある種のグローバルに一定の種類のものであり、次のようなものに決めました。

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'])

警告:かなり厳しいトレードオフが生じるため、ほとんどのユースケースではこれをお勧めしません。

0
Adverbly

サブクラス化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()(py2のiteritems())およびdictからのすべての特典のサポートそれらを明示的に実装せずにボックス
  • 内部的にdictを使用します。これはパフォーマンスを意味します(dictはCPythonのcで記述されています)
  • エレガントでシンプル、ブラックマジックなし
  • isinstance(my_frozen_dict, dict)はTrueを返します-pythonは duck-typing を推奨しますが、多くのパッケージはisinstance()を使用しますが、これにより多くの調整とカスタマイズを節約できます

短所

  • サブクラスはこれをオーバーライドしたり、内部でアクセスしたりできます(Pythonで何かを100%保護することはできません。ユーザーを信頼し、適切なドキュメントを提供する必要があります)。
  • 速度を重視する場合は、___hash___を少し速くしたいかもしれません。
0
ShmulikA