「凍結された口述」とは何でしょうか?


158
  • フリーズセットは、フリーズセットです。
  • フリーズされたリストはタプルになる可能性があります。
  • 凍結されたdictは何でしょうか?不変でハッシュ可能な辞書。

私はそれがのようなものかもしれないと思いますがcollections.namedtuple、それは凍結キー辞書(半凍結辞書)に似ています。だよね?

「frozendict」凍結された辞書である必要があり、それが持っている必要がありkeysvaluesget、など、サポートinforなど

更新:
*ありますhttps : //www.python.org/dev/peps/pep-0603

回答:


120

Pythonには組み込みのfrozendict型はありません。これはあまり頻繁には役に立たないことがわかります(ただし、それでもおそらくそれよりも頻繁に役立つでしょうfrozenset)。

このような型が必要になる最も一般的な理由は、引数が不明な関数の関数呼び出しをメモする場合です。(値がハッシュ可能である)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'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'

7
この種のことで人々が心配するスレッドセーフティのレベルはわかりませんが、その点で__hash__メソッドを少し改善することができます。ハッシュを計算するときに一時変数を使用するだけでself._hash、最終的な値を取得したときにのみ設定されます。このようにして、最初のスレッドが計算中にハッシュを取得する別のスレッドは、誤った値を取得するのではなく、単に冗長な計算を実行します。
Jeff DQ

22
@Jeff原則として、すべてのコードはどこでもスレッドセーフではありません。そのコードを安全に使用するには、いくつかの同期構造にラップする必要があります。また、スレッドセーフの特定の概念は、オブジェクト属性割り当てのアトミック性に依存しており、これは保証されていません。
Devin Jeanpierre

9
@Anentropic、それはまったく真実ではありません。
Mike Graham、

17
警告:この「FrozenDict」は必ずしも凍結されているわけではありません。可変リストを値として設定することを妨げるものは何もありません。その場合、ハッシュはエラーをスローします。これには必ずしも問題はありませんが、ユーザーは注意する必要があります。もう1つ:このハッシュアルゴリズムは選択が不十分で、ハッシュの衝突が発生しやすくなっています。たとえば、{'a': 'b'}は{'b': 'a'}と同じハッシュであり、{'a':1、 'b':2}は{'a':2、 'と同じハッシュですb ':1}。より適切な選択はselfです。_hash^ = hash((key、value))
Steve Byrnes

6
不変オブジェクトに可変エントリを追加する場合、2つの可能な動作は、オブジェクトの作成時にエラーをスローすること、またはオブジェクトのハッシュ時にエラーをスローすることです。タプルは後者を行い、frozensetは前者を行います。すべてを考慮した上で、後者のアプローチを採用することを決定したと思います。それでも、FrozenDictとfrozensetの名前が似ていることに気づき、同じように動作するはずであるという結論にジャンプするかもしれません。したがって、この違いについて人々に警告する価値があると思います。:-)
Steve Byrnes

63

不思議なことに、frozensetPythonではめったに役に立ちませんが、凍結されたマッピングはまだありません。このアイデアはPEP 416で却下されました-frozendict組み込み型を追加してください。このアイデアはPython 3.9で再検討される可能性があります。PEP603-コレクションへの凍結マップタイプの追加を参照してください。

したがって、これに対する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されますが、マッピングプロキシオブジェクト自体に書き込むことはできません。

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


2
プロキシをモジュール変数に格納する特別な理由はありますか?なぜdef foo(config=MappingProxyType({'a': 1})):ですか?あなたの例でも、によるグローバルな変更が可能default_configです。
jpmc26 2018年

また、二重の割り当てconfig = default_config = {'a': 1}はタイプミスだと思います。
jpmc26 2018年

21

辞書のキーと値がそれ自体不変(たとえば文字列)であると仮定すると、次のようになります。

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

これは、良い、正規の、不変のdictの表現です(異常な比較動作がソートをめちゃくちゃにしない限り)。
マイクグラハム

6
@devin:完全に同意しましたが、もっと良い方法がしばしばあることを例として、投稿を立たせます。
msw

14
キーまたは値に一貫した順序を定義する必要がないフローズンセットに配置するのがさらに良いでしょう。
asmeurer

7
これに関する唯一の問題:もはやマッピングがありません。それが、そもそもフリーズされたディクテーションを持つことの要点です。
Mad Physicist

2
この方法は、dictに戻るときに本当に便利です。単にdict(t)
codythecoder

12

はありませんがfronzedictMappingProxyTypePython 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})

警告付き:TypeError: can't pickle mappingproxy objects
Radu

私はこれの考えが好きです。やってみるつもりです。
Doug、

10

これが私が使っているコードです。私は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)

1
ここに投稿することにより、CC BY-SA 3.0でライセンスを取得していることに注意してください。少なくともそれが一般的な見方です。法的な根拠は、最初にサインアップしたときにいくつかのT&Cに同意することだと思います。
Evgeni Sergeev 2015年

1
私は、口述をせずにキーハッシュを検索する方法を考えようとして頭を痛めました。のハッシュをItemキーのハッシュに再定義することは、きちんとしたハックです!
2016

残念ながら、実行時間はdiff(diff({key}))FrozenDictのサイズに比例していますが、通常のdictアクセス時間は平均的なケースでは一定です。
デニス

6

次のような関数を書くたびに、frozendictについて考えます。

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

6
このようなコメントを見るたびに、どこかで失敗してデフォルトとして{}を設定し、戻って最近作成したコードを確認しているはずです。
Ryan Hiebert、2011

1
ええ、それは遅かれ早かれ誰もが遭遇する厄介な落とし穴です。
Mark Visser、2012年

8
より簡単な定式化:optional_dict_parm = optional_dict_parm or {}
Emmanuel

2
この場合、引数のデフォルト値として使用できます。types.MappingProxyType({})
GingerPlusPlus

@GingerPlusPlus答えとして書いていただけますか?
jonrsharpe 2015年

5

次のようfrozendictutilspieパッケージから使用できます。

>>> 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を返します



3

はい、これは私の2番目の答えですが、まったく異なるアプローチです。最初の実装は純粋なpythonでした。これはCythonにあります。Cythonモジュールの使用方法とコンパイル方法を知っている場合、これは通常の辞書と同じくらい高速です。単一の値を取得するには、おおよそ.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

の主な欠点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

もちろん、これはフラットな辞書に対してのみ機能しますが、再帰バージョンを実装することはそれほど難しくありません。


1
他のタプルの回答と同じ問題:のgetattr(fa, x)代わりに行う必要がありfa[x]keys指先でメソッドを実行する必要がなく、マッピングが望ましい他のすべての理由。
Mad Physicist

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に使用することはパフォーマンスを意味します(dictCPythonではcで書かれています)
  • エレガントでシンプル、黒魔術なし
  • isinstance(my_frozen_dict, dict)Trueを返します-Pythonは多くのパッケージの使用をダックタイピングすることを推奨しますがisinstance()、これにより多くの微調整とカスタマイズを節約できます

短所

  • どのサブクラスもこれをオーバーライドしたり、内部的にアクセスしたりできます(Pythonで実際に何かを100%保護することはできません。ユーザーを信頼し、適切なドキュメントを提供する必要があります)。
  • スピードが気になるなら__hash__、もう少し速くしたいかもしれません。

私は別のスレッドで速度の比較を行いましたが、オーバーライド__setitem__と継承dictは他の多くの方法に比べて非常に速いことがわかりました。
11:44に


0

ある種のグローバル定数のようなもののために、ある時点で固定キーにアクセスする必要があり、私はこのようなものに落ち着きました:

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

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


以下は、パフォーマンスを犠牲にすることなく、同等のパワーになります。ただし、これは受け入れられた回答を単純化したものです... `` `class FrozenDict:def __init __(self、data):self._data = data def __getitem __(self、key):return self._data [key]` ` `
ユバル

その答えは同等ではありません。初心者の場合、APIは初期化するためにデータを必要とするため、異なります。これはまた、もはやグローバルにアクセスできないことを意味します。さらに、_dataが変更されると、戻り値が変化します。私はかなりのトレードオフがあることを知っています-言ったように、私はほとんどのユースケースでこれをお勧めしません。
副詞

-1

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

frozen_dictクラスはハッシュ可能ではありません
miracle173
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.