development

"동결 된 dict"은 무엇입니까?

big-blog 2020. 6. 23. 21:40
반응형

"동결 된 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:
            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파이썬 에는 거의 유용하지 않지만 여전히 얼어 붙은 매핑은 없습니다. 이 아이디어는 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를 반환합니다.


네, 이것은 두 번째 대답이지만 완전히 다른 접근법입니다. 첫 번째 구현은 순수한 파이썬이었습니다. 이것은 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을 설치 한 경우 위의 두 파일을 동일한 디렉토리에 저장하십시오. 명령 행에서 해당 디렉토리로 이동하십시오.

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

그리고 당신은해야합니다.


가장 큰 단점 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

물론 이것은 단순한 dicts에 대해서만 작동하지만 재귀 버전을 구현하는 것은 어렵지 않아야합니다.


frozendict 설치

pip install frozendict

그걸 써!

from frozendict import frozendict

def smth(param = frozendict({})):
    pass

다른 옵션은 패키지 MultiDictProxy클래스입니다 multidict.


서브 클래 싱 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)파이썬은 많은 패키지 사용을 오리 타이핑 하도록 권장하지만 isinstance()많은 조정 및 사용자 정의를 저장할 수 있습니다

단점

  • 모든 하위 클래스는 이것을 재정의하거나 내부적으로 액세스 할 수 있습니다 (파이썬에서 무언가를 실제로 100 % 보호 할 수는 없으며 사용자를 신뢰하고 좋은 문서를 제공해야합니다).
  • 속도를 걱정한다면 __hash__조금 더 빨리 만들고 싶을 수도 있습니다 .

자국어 지원이없는 경우 직접 수행하거나 기존 솔루션을 사용할 수 있습니다. 다행스럽게도 파이썬은 기본 구현을 확장하는 것을 간단하게 만듭니다.

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

경고 : 대부분의 유스 케이스에는 약간의 절충이 있으므로 권장하지 않습니다.

참고 URL : https://stackoverflow.com/questions/2703599/what-would-a-frozen-dict-be

반응형