web-dev-qa-db-fra.com

Que serait un "dict gelé"?

  • Un ensemble congelé est un frozenset.
  • Une liste figée pourrait être un tuple.
  • Que serait un dict gelé? Un dict immuable, lavable.

Je suppose que cela pourrait être quelque chose comme collections.namedtuple, mais cela ressemble plus à un dict-clef figé (un dicté semi-figé). N'est-ce pas?

Un "frozendict" devrait être un dictionnaire figé, il devrait avoir keys, values, get, etc., et prendre en charge in, for, etc.

136
dugres

Python n'a pas de type intégré frozendict. Il s'avère que cela ne serait pas utile trop souvent (bien que cela le soit probablement encore plus souvent que frozenset.

La raison la plus courante de vouloir un tel type est lorsque la fonction de mémorisation appelle des fonctions avec des arguments inconnus. La solution la plus courante pour stocker un équivalent hashable d'un dict (où les valeurs sont hashable) est quelque chose comme Tuple(sorted(kwargs.iteritems())).

Cela dépend du fait que le tri ne soit pas un peu fou. Python ne peut pas promettre positivement que le tri aboutira à quelque chose de raisonnable ici).


Vous pouvez facilement créer un emballage qui fonctionne comme un dict. Cela pourrait ressembler à quelque chose comme

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

Cela devrait bien fonctionner:

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

Curieusement, bien que nous ayons le frozenset rarement utile en python, il n’ya toujours pas de mapping gelé. L'idée a été rejetée dans PEP 416 .

Donc la python 2 solution à ceci:

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

Semble encore être le peu boiteux:

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

En python3, vous avez l'option de this :

from types import MappingProxyType

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

def foo(config=DEFAULTS):
    ...

Maintenant la configuration par défaut peut être mise à jour dynamiquement, mais reste immuable là où vous voulez qu'elle soit immuable en passant par le proxy.

Donc, les changements dans le default_config mettra à jour DEFAULTS comme prévu, mais vous ne pouvez pas écrire dans l’objet proxy de mappage lui-même.

Certes, ce n'est pas tout à fait la même chose qu'un "dict immuable, lavable" - mais c'est un substitut décent étant donné le même genre de cas d'utilisation pour lesquels nous pourrions vouloir un frozendict.

51
wim

En supposant que les clés et les valeurs du dictionnaire sont elles-mêmes immuables (par exemple, des chaînes de caractères), alors:

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

Il n'y a pas fronzedict, mais vous pouvez utiliser MappingProxyType qui a été ajouté à la bibliothèque standard avec 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})
11
Julio Marins

Voici le code que j'ai utilisé. J'ai sous-classé Frozenset. Les avantages de ceci sont les suivants.

  1. C'est un objet vraiment immuable. Ne comptez pas sur le bon comportement des futurs utilisateurs et développeurs.
  2. Il est facile de convertir un dictionnaire ordinaire en un dictionnaire figé. FrozenDict (orig_dict) -> dictionnaire figé. dict (frozen_dict) -> dict régulier.

Mise à jour du 21 janvier 2015: Le code original que j'ai posté en 2014 utilisait une boucle for pour trouver une clé correspondante. C'était incroyablement lent. Maintenant, j'ai mis au point une implémentation qui tire parti des fonctionnalités de hachage de frozenset. Les paires clé-valeur sont stockées dans des conteneurs spéciaux où le __hash__ et __eq__ Les fonctions ne sont basées que sur la touche. Ce code a également fait l'objet de tests unitaires, contrairement à ce que j'ai publié ici en août 2014.

Licence de style 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

Je pense à frozendict chaque fois que j'écris une fonction comme celle-ci:

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

Vous pouvez utiliser le package frozendict de utilspie sous la forme:

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

Selon le document :

frozendict (dict_obj) : accepte les obj de type dict et retourne un dict hashable et immuable

4
Moinuddin Quadri

Oui, c’est ma deuxième réponse, mais c’est une approche complètement différente. La première implémentation était en python pur. Celui-ci est en Cython. Si vous savez utiliser et compiler des modules Cython, c’est aussi rapide qu’un dictionnaire ordinaire. Environ 0,04 à 0,06 micro-seconde pour récupérer une valeur unique.

C'est le fichier "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']

Voici le fichier "setup.py"

from distutils.core import setup
from Cython.Build import cythonize

setup(
    ext_modules = cythonize('frozen_dict.pyx')
)

Si vous avez installé Cython, enregistrez les deux fichiers ci-dessus dans le même répertoire. Déplacer vers ce répertoire dans la ligne de commande.

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

Et vous devriez avoir fini.

3
Steve Zelaznik

Le principal inconvénient de namedtuple est qu'il doit être spécifié avant son utilisation, ce qui le rend moins pratique pour les cas à usage unique.

Cependant, il existe une solution de contournement pratique qui peut être utilisée pour traiter de nombreux cas de ce type. Disons que vous voulez avoir un équivalent immuable du dict suivant:

MY_CONSTANT = {
    'something': 123,
    'something_else': 456
}

Ceci peut être imité comme ceci:

from collections import namedtuple

MY_CONSTANT = namedtuple('MyConstant', 'something something_else')(123, 456)

Il est même possible d'écrire une fonction auxiliaire pour automatiser ceci:

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

Bien sûr, cela ne fonctionne que pour les dits plats, mais il ne devrait pas être trop difficile d'implémenter une version récursive.

3
Berislav Lopac

Installer frozendict

pip install frozendict

Utilise le!

from frozendict import frozendict

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

Une autre option est la classe MultiDictProxy du package multidict .

1
Berislav Lopac

En l'absence de prise en charge de la langue maternelle, vous pouvez le faire vous-même ou utiliser une solution existante. Heureusement, Python permet de déployer simplement leurs implémentations de base.

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

J'avais besoin d'accéder à des clés fixes pour quelque chose à un moment donné pour quelque chose qui était une sorte de chose globalement constante et je me suis décidé sur quelque chose comme ceci:

class MyFrozenDict:
    def __getitem__(self, key):
        if key == 'mykey1':
            return 0
        if key == 'mykey2':
            return "another value"
        raise KeyError(key)

Utilisez-le comme

a = MyFrozenDict()
print(a['mykey1'])

AVERTISSEMENT: je ne le recommande pas dans la plupart des cas d'utilisation car cela entraîne des compromis assez importants.

0
Adverbly

Sous-classement dict

je vois ce modèle dans la nature (github) et je voulais le mentionner:

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

exemple d'utilisation:

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

  • support pour get(), keys(), items() (iteritems() sur py2) et tous les goodies de dict sur le box sans les implémenter explicitement
  • utilise en interne dict, ce qui signifie performance (dict est écrit en c dans CPython)
  • élégant simple et pas de magie noire
  • isinstance(my_frozen_dict, dict) retourne True - bien que python encourage typage de canard) de nombreux paquets utilisent isinstance(), cela peut économiser de nombreuses modifications et personnalisations.

Inconvénients

  • n'importe quelle sous-classe peut remplacer ceci ou y accéder en interne (vous ne pouvez pas vraiment protéger à 100% quelque chose en python, vous devez faire confiance à vos utilisateurs et fournir une bonne documentation).
  • si vous tenez à la vitesse, vous voudrez peut-être rendre __hash__ un peu plus rapide.
0
ShmulikA