[python] “동결 된 dict”은 무엇입니까?

  • 고정 세트는 고정 세트입니다.
  • 고정 된 목록은 튜플 일 수 있습니다.
  • 얼어 붙은 구술은 무엇입니까? 불변의 해시 가능한 dict.

나는 그것이 같은 것일 수 있다고 생각 collections.namedtuple하지만, 그것은 고정 키 dict (반 냉동 dict)와 비슷합니다. 그렇지 않습니까?

A “frozendict는”이 있어야한다, 냉동 사전되어야한다 keys, values, get, 등 및 지원 in, for

업데이트 :
* 있습니다 : https://www.python.org/dev/peps/pep-0603



답변

파이썬에는 내장 된 dictdict 유형이 없습니다. 이것은 너무 자주 유용하지 않을 것입니다 (아마도 여전히보다 자주 유용 할 것 frozenset입니다).

이러한 유형을 원하는 가장 일반적인 이유는 함수를 메모리에 저장하면 알 수없는 인수를 가진 함수를 호출 할 때입니다. dict과 같은 해시 가능 값을 저장하는 가장 일반적인 솔루션 (값이 해시 가능)은 다음과 같습니다 tuple(sorted(kwargs.iteritems())).

정렬이 약간 미쳤지 않은지에 따라 다릅니다. 파이썬은 정렬이 여기에 합리적인 결과를 가져올 것이라고 긍정적으로 약속 할 수 없습니다. (그러나 그것은 다른 많은 것을 약속 할 수 없으므로 너무 땀을 흘리지 마십시오.)


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'


답변

흥미롭게도 frozenset파이썬 에는 거의 유용하지 않지만 여전히 얼어 붙은 매핑은 없습니다. 이 아이디어는 PEP 416 에서 거부되었습니다 . 고정 된 내장 유형을 추가하십시오 . 이 아이디어는 Python 3.9에서 다시 확인할 수 있습니다. PEP 603-컬렉션에 고정 된 맵 유형 추가를 참조하십시오 .

따라서 파이썬 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”와 같은 것은 아니지만-우리가 고정 된 dict를 원할 수도있는 같은 종류의 유스 케이스를 감안할 때 적절한 대체물입니다.


답변

사전의 키와 값을 변경할 수 없다고 가정하면 (예 : 문자열) :

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


답변

는 없지만 Python 3.3으로 표준 라이브러리에 추가 된 것을 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})


답변

내가 사용한 코드는 다음과 같습니다. 나는 고정 세트를 서브 클래 싱했다. 이것의 장점은 다음과 같습니다.

  1. 이것은 정말 불변의 객체입니다. 미래의 사용자와 개발자의 좋은 행동에 의존하지 않습니다.
  2. 일반 사전과 고정 된 사전간에 쉽게 전환 할 수 있습니다. FrozenDict (orig_dict)-> 고정 된 사전. dict (frozen_dict)-> 일반 dict.

2015 년 1 월 21 일 업데이트 : 2014 년에 게시 한 원래 코드는 for-loop를 사용하여 일치하는 키를 찾았습니다. 그것은 엄청나게 느렸다. 이제 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)


답변

다음과 같은 함수를 작성할 때마다 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) : dict 유형의 obj를 허용하고 해시 가능하고 불변의 dict를 반환합니다.