Czym byłby „zamrożony dyktat”?

158
  • Zamrożony zestaw to zamrożony zestaw.
  • Zamrożona lista może być krotką.
  • Czym byłby zamrożony dyktando? Niezmienny, haszowalny dykt.

Myślę, że może to być coś podobnego collections.namedtuple, ale to bardziej przypomina dyktowanie zamrożonych kluczy (półzamrożone dyktowanie). Prawda?

A „frozendict” powinny być zamrożone słownik, powinien on mieć keys, values, get, itd., A także wsparcie in,for itp

aktualizacja:
* tam jest: https://www.python.org/dev/peps/pep-0603

dugres
źródło

Odpowiedzi:

120

Python nie ma wbudowanego typu Frozendict. Okazuje się, że nie byłoby to przydatne zbyt często (chociaż prawdopodobnie byłoby to przydatne częściej niż frozensetjest).

Najczęstszym powodem, dla którego warto chcieć takiego typu jest zapamiętywanie wywołań funkcji z nieznanymi argumentami. Najczęstszym rozwiązaniem do przechowywania hashowalnego odpowiednika dyktu (gdzie wartości są hashowalne) jest coś takiego tuple(sorted(kwargs.iteritems())).

To zależy od tego, czy sortowanie nie jest trochę szalone. Python nie może obiecać, że sortowanie przyniesie tutaj coś rozsądnego. (Ale nie może obiecać nic więcej, więc nie przejmuj się zbytnio).


Możesz łatwo stworzyć jakiś rodzaj opakowania, które działa podobnie jak dykt. To może wyglądać jak

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

Powinno działać świetnie:

>>> 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'
Mike Graham
źródło
7
Nie wiem, o jaki poziom bezpieczeństwa nici ludzie martwią się przy tego typu rzeczach, ale pod tym względem Wasza __hash__metoda mogłaby zostać nieco ulepszona. Po prostu użyj zmiennej tymczasowej podczas obliczania skrótu i ​​ustaw ją dopiero self._hashpo uzyskaniu ostatecznej wartości. W ten sposób inny wątek otrzymujący hash podczas obliczania pierwszego po prostu wykona zbędne obliczenia, zamiast uzyskać nieprawidłową wartość.
Jeff DQ
22
@Jeff Z reguły cały kod wszędzie nie jest bezpieczny dla wątków i powinieneś owinąć go wokół niektórych struktur synchronizacyjnych, aby bezpiecznie używać tego kodu. Ponadto, twoje szczególne pojęcie bezpieczeństwa wątków opiera się na atomowości przypisywania atrybutów obiektu, co jest dalekie od zagwarantowania.
Devin Jeanpierre
9
@Anentropic, to wcale nie jest prawda.
Mike Graham
17
Uwaga: ten „FrozenDict” niekoniecznie jest zamrożony. Nic nie stoi na przeszkodzie, aby umieścić zmienną listę jako wartość, w którym to przypadku haszowanie zwróci błąd. Nie ma w tym nic złego, ale użytkownicy powinni być świadomi. Inna sprawa: ten algorytm haszowania jest źle dobrany, bardzo podatny na kolizje haszowania. Na przykład {'a': 'b'} hashuje tak samo jak {'b': 'a'} i {'a': 1, 'b': 2} hashuje to samo co {'a': 2, ' b ': 1}. Lepszym wyborem byłoby self._hash ^ = hash ((klucz, wartość))
Steve Byrnes
6
Jeśli dodasz zmienny wpis w niezmiennym obiekcie, dwa możliwe zachowania to zgłoszenie błędu podczas tworzenia obiektu lub zgłoszenie błędu podczas haszowania obiektu. Krotki robią to drugie, Frozenset robi to pierwsze. Zdecydowanie uważam, że podjąłeś dobrą decyzję, biorąc pod uwagę to drugie podejście. Niemniej jednak myślę, że ludzie mogą zobaczyć, że FrozenDict i frozenset mają podobne nazwy i dojść do wniosku, że powinni zachowywać się podobnie. Myślę więc, że warto ostrzec ludzi o tej różnicy. :-)
Steve Byrnes
63

Co ciekawe, chociaż rzadko mamy przydatne frozensetw Pythonie, nadal nie ma zamrożonego mapowania. Pomysł został odrzucony w PEP 416 - Dodaj wbudowany typ frozendict . Pomysł może zostać ponownie przeanalizowany w Pythonie 3.9, zobacz PEP 603 - Dodawanie typu frozenmap do kolekcji .

Więc rozwiązanie Pythona 2 do tego:

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

Nadal wydaje się być nieco kulawy:

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

W python3 masz możliwość to :

from types import MappingProxyType

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

def foo(config=DEFAULTS):
    ...

Teraz domyślna konfiguracja może być aktualizowana dynamicznie, ale pozostaje niezmienna tam, gdzie ma być niezmienna, zamiast tego przekazując serwer proxy.

Zatem zmiany w default_configaktualizacji zostaną zaktualizowane DEFAULTSzgodnie z oczekiwaniami, ale nie można pisać do samego obiektu proxy mapowania.

Wprawdzie to nie to samo, co „niezmienny, haszowalny dykt” - ale jest to przyzwoity substytut, biorąc pod uwagę te same przypadki użycia, dla których moglibyśmy chcieć zamrożonego zakończenia.

wim
źródło
2
Czy istnieje jakiś szczególny powód, aby przechowywać proxy w zmiennej modułu? Dlaczego nie po prostu def foo(config=MappingProxyType({'a': 1})):? Twój przykład nadal umożliwia również globalną modyfikację default_config.
jpmc26
Podejrzewam też, że podwójne przypisanie w config = default_config = {'a': 1}to błąd.
jpmc26
21

Zakładając, że klucze i wartości słownika są same w sobie niezmienne (np. Stringi) to:

>>> 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
msw
źródło
To jest dobra, kanoniczna, niezmienna reprezentacja dyktatu (z wyjątkiem szalonych zachowań porównawczych, które psują sort).
Mike Graham
6
@devin: w pełni się zgodził, ale postaram się, aby mój post stanowił przykład, że często istnieje jeszcze lepszy sposób.
msw
14
Jeszcze lepiej byłoby umieścić go w zamrożonym zestawie, który nie wymaga, aby klucze lub wartości miały zdefiniowaną spójną kolejność.
asmeurer
7
Jest tylko jeden problem: nie masz już mapowania. To byłby główny sens posiadania zamrożonego dyktu w pierwszej kolejności.
Mad Physicist
2
Ta metoda jest naprawdę fajna, gdy wracamy do dyktowania. po prostudict(t)
codythecoder
12

Nie ma fronzedict, ale możesz użyć MappingProxyTypetego, co zostało dodane do standardowej biblioteki w Pythonie 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})
Julio Marins
źródło
z zastrzeżeniem:TypeError: can't pickle mappingproxy objects
Radu
Podoba mi się ten pomysł. Spróbuję.
Doug,
10

Oto kod, którego używałem. Podklasa zamrożona. Zalety tego są następujące.

  1. To jest naprawdę niezmienny przedmiot. Żadnego polegania na dobrym zachowaniu przyszłych użytkowników i programistów.
  2. Łatwo jest konwertować między zwykłym słownikiem a zamrożonym słownikiem. FrozenDict (orig_dict) -> zamrożony słownik. dict (frozen_dict) -> zwykły dict.

Aktualizacja, 21 stycznia 2015 r .: Oryginalny fragment kodu, który opublikowałem w 2014 r., Używał pętli for, aby znaleźć pasujący klucz. To było niesamowicie powolne. Teraz przygotowałem implementację, która korzysta z funkcji mieszania Frozenset. Pary klucz-wartość są przechowywane w specjalnych kontenerach, w których __hash__i__eq__ funkcje są oparte tylko na kluczu. Ten kod został również formalnie przetestowany jednostkowo, w przeciwieństwie do tego, co opublikowałem tutaj w sierpniu 2014 r.

Licencja typu 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)
Steve Zelaznik
źródło
1
Pamiętaj, że masz również licencję na licencji CC BY-SA 3.0, zamieszczając ją tutaj. Przynajmniej taki jest powszechny pogląd . Wydaje mi się, że podstawą prawną tego jest wyrażenie zgody na niektóre Warunki podczas rejestracji.
Evgeni Sergeev
1
Złamałem mózg, próbując wymyślić sposób na wyszukanie klucza bez dyktowania. Przedefiniowanie skrótu Itemklucza jako skrótu klucza to zgrabny hack!
clacke
Niestety, czas wykonywania programu diff(diff({key}))nadal jest liniowy w stosunku do rozmiaru FrozenDict, podczas gdy zwykły czas dostępu do dyktowania jest stały w przeciętnym przypadku.
Dennis
6

Myślę o frozendict za każdym razem, gdy piszę taką funkcję:

def do_something(blah, optional_dict_parm=None):
    if optional_dict_parm is None:
        optional_dict_parm = {}
Mark Visser
źródło
6
Za każdym razem, gdy widzę taki komentarz, jestem pewien, że gdzieś schrzaniłem i wstawiłem {} jako domyślny, a potem wracam i patrzę na mój niedawno napisany kod.
Ryan Hiebert
1
Tak, to paskudna łapanka, na którą wszyscy wpadają, prędzej czy później.
Mark Visser,
8
Łatwiejsze sformułowanie:optional_dict_parm = optional_dict_parm or {}
Emmanuel
2
W takim przypadku możesz użyć domyślnej wartości argumentu. types.MappingProxyType({})
GingerPlusPlus
@GingerPlusPlus czy mógłbyś to napisać jako odpowiedź?
jonrsharpe
5

Możesz użyć frozendictz utilspiepakietu jako:

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

Zgodnie z dokumentem :

frozendict (dict_obj) : akceptuje obj typu dict i zwraca hashable i immutable dict

Moinuddin Quadri
źródło
5

Zainstaluj frozendict

pip install frozendict

Użyj tego!

from frozendict import frozendict

def smth(param = frozendict({})):
    pass
Andrey Korchak
źródło
3

Tak, to moja druga odpowiedź, ale to zupełnie inne podejście. Pierwsza implementacja była w czystym Pythonie. Ten jest w Cythonie. Jeśli wiesz, jak używać i kompilować moduły Cython, jest to tak samo szybkie, jak zwykły słownik. Około 0,04 do 0,06 mikrosekundy, aby pobrać pojedynczą wartość.

To jest plik „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']

Oto plik „setup.py”

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

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

Jeśli masz zainstalowany Cython, zapisz dwa powyższe pliki w tym samym katalogu. Przejdź do tego katalogu w wierszu poleceń.

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

I powinno być gotowe.

Steve Zelaznik
źródło
3

Główną wadą programu namedtuplejest to, że należy go określić przed użyciem, więc jest mniej wygodny w przypadkach jednorazowego użytku.

Istnieje jednak praktyczne obejście, które można zastosować w wielu takich przypadkach. Załóżmy, że chcesz mieć niezmienny odpowiednik następującego dyktowania:

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

Można to emulować w następujący sposób:

from collections import namedtuple

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

Możliwe jest nawet napisanie funkcji pomocniczej, aby zautomatyzować to:

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

Oczywiście działa to tylko dla płaskich dykt, ale implementacja wersji rekurencyjnej nie powinna być zbyt trudna.

Berislav Lopac
źródło
1
Ten sam problem, co w przypadku innej odpowiedzi na krotkę: getattr(fa, x)zamiast tego musisz zrobić fa[x], żadnej keysmetody na wyciągnięcie ręki, a wszystkie inne powody, dla których mapowanie może być pożądane.
Mad Physicist
1

Podklasy dict

widzę ten wzór na wolności (github) i chciałem o nim wspomnieć:

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

przykład użycia:

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

Plusy

  • wsparcie get(), keys(), items()( iteritems()na Py2) i wszystkie gadżety zdict wyjęciu z pudełka, bez wyraźnego ich realizacji
  • używa wewnętrznie, dictco oznacza wydajność (dict jest napisane w c w CPythonie)
  • eleganckie proste i bez czarnej magii
  • isinstance(my_frozen_dict, dict)zwraca True - chociaż Python zachęca do pisania kaczkami w wielu pakietach isinstance(), może to zaoszczędzić wiele poprawek i dostosowań

Cons

  • każda podklasa może to przesłonić lub uzyskać do niego dostęp wewnętrznie (nie możesz naprawdę w 100% chronić czegoś w Pythonie, powinieneś ufać swoim użytkownikom i zapewnić dobrą dokumentację).
  • jeśli zależy ci na szybkości, możesz chcieć __hash__trochę przyspieszyć.
ShmulikA
źródło
Zrobiłem porównanie szybkości w innym wątku i okazuje się, że nadpisywanie __setitem__i dziedziczenie dictjest niesamowicie szybkie w porównaniu z wieloma alternatywami.
Torxed
0

Inną opcją jest MultiDictProxyklasa z multidictpakietu.

Berislav Lopac
źródło
1
Niestety nie da się tego hashować.
rominf
0

W pewnym momencie musiałem uzyskać dostęp do stałych kluczy do czegoś, co było czymś w rodzaju globalnej trwałości i zdecydowałem się na coś takiego:

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

Użyj tego jak

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

OSTRZEŻENIE: Nie polecam tego w większości przypadków użycia, ponieważ powoduje to dość poważne kompromisy.

Adverely
źródło
Poniższe byłyby równe pod względem mocy bez skaryfikacji wydajności. Jest to jednak tylko uproszczenie zaakceptowanej odpowiedzi ... `` class FrozenDict: def __init __ (self, data): self._data = data def __getitem __ (self, key): return self._data [key] '' `
Yuval
@Yuval, ta odpowiedź nie jest równoważna. Na początek interfejs API jest inny, ponieważ potrzebuje danych do zainicjowania. Oznacza to również, że nie jest już dostępny globalnie. Ponadto, jeśli _data jest zmutowane, zmienia się zwracana wartość. Zdaję sobie sprawę, że istnieją znaczne kompromisy - jak powiedziałem, nie polecam tego w większości przypadków użycia.
Adverbly
-1

W przypadku braku obsługi języka ojczystego możesz to zrobić samodzielnie lub skorzystać z istniejącego rozwiązania. Na szczęście Python sprawia, że ​​rozszerzenie ich podstawowych implementacji jest bardzo proste.

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
wypływający
źródło
Twoja klasa frozen_dict nie jest
hashowana