Was wäre ein "eingefrorenes Diktat"?

158
  • Ein gefrorenes Set ist ein Frozenset.
  • Eine eingefrorene Liste könnte ein Tupel sein.
  • Was wäre ein eingefrorenes Diktat? Ein unveränderliches, hashbares Diktat.

Ich denke, es könnte so etwas sein collections.namedtuple, aber das ist eher ein Diktat mit eingefrorenen Schlüsseln (ein Diktat mit halbgefrorenem Schlüssel). Ist es nicht?

A "frozendict" sollte ein gefrorenes Wörterbuch sein, sollte es haben keys, values, getetc. und Unterstützung in, foretc.

Update:
* da ist es: https://www.python.org/dev/peps/pep-0603

Dugres
quelle

Antworten:

120

Python hat keinen eingebauten Frozendict-Typ. Es stellt sich heraus, dass dies nicht zu oft nützlich wäre (obwohl es wahrscheinlich immer noch öfter nützlich wäre als es frozensetist).

Der häufigste Grund, einen solchen Typ zu wollen, besteht darin, dass beim Speichern von Funktionsfunktionen Funktionen mit unbekannten Argumenten aufgerufen werden. Die häufigste Lösung , die ein hashable Äquivalent eines dict zu speichern (wobei die Werte hashable sind) ist so etwas wie tuple(sorted(kwargs.iteritems())).

Dies hängt davon ab, dass die Sortierung nicht ein bisschen verrückt ist. Python kann nicht positiv versprechen, dass das Sortieren hier zu etwas Vernünftigem führt. (Aber es kann nicht viel anderes versprechen, also schwitzen Sie nicht zu viel.)


Sie könnten leicht genug eine Art Wrapper erstellen, der ähnlich wie ein Diktat funktioniert. Es könnte ungefähr so ​​aussehen

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

Es sollte großartig funktionieren:

>>> 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
quelle
7
Ich weiß nicht, über welches Maß an Thread-Sicherheit sich die Leute bei solchen Dingen Sorgen machen, aber in dieser Hinsicht könnte Ihre __hash__Methode leicht verbessert werden. Verwenden Sie bei der Berechnung des Hashs einfach eine temporäre Variable und setzen self._hashSie diese erst, wenn Sie den endgültigen Wert haben. Auf diese Weise führt ein anderer Thread, der einen Hash erhält, während der erste berechnet, einfach eine redundante Berechnung durch, anstatt einen falschen Wert zu erhalten.
Jeff DQ
22
@Jeff In der Regel ist der gesamte Code nicht überall threadsicher, und Sie sollten ihn um einige Synchronisationsstrukturen wickeln, um diesen Code sicher zu verwenden. Außerdem hängt Ihr spezieller Begriff der Thread-Sicherheit von der Atomizität der Objektattributzuweisung ab, die bei weitem nicht garantiert ist.
Devin Jeanpierre
9
@Anentropic, das stimmt überhaupt nicht.
Mike Graham
17
Seien Sie gewarnt: Dieses "FrozenDict" ist nicht unbedingt eingefroren. Nichts hindert Sie daran, eine veränderbare Liste als Wert zu setzen. In diesem Fall führt Hashing zu einem Fehler. Daran ist nicht unbedingt etwas auszusetzen, aber die Benutzer sollten sich dessen bewusst sein. Eine andere Sache: Dieser Hashing-Algorithmus ist schlecht gewählt und sehr anfällig für Hash-Kollisionen. Zum Beispiel hasst {'a': 'b'} dasselbe wie {'b': 'a'} und {'a': 1, 'b': 2} dasselbe wie {'a': 2, ' b ': 1}. Bessere Wahl wäre self._hash ^ = Hash ((Schlüssel, Wert))
Steve Byrnes
6
Wenn Sie einem unveränderlichen Objekt einen veränderlichen Eintrag hinzufügen, können Sie beim Erstellen des Objekts einen Fehler oder beim Hashing des Objekts einen Fehler auslösen. Tupel machen das letztere, Frozenset macht das erstere. Ich denke auf jeden Fall, dass Sie eine gute Entscheidung getroffen haben, den letzteren Ansatz zu wählen, alles in allem. Trotzdem denke ich, dass die Leute vielleicht sehen, dass FrozenDict und frozenset ähnliche Namen haben, und zu dem Schluss kommen, dass sie sich ähnlich verhalten sollten. Ich denke, es lohnt sich, die Leute vor diesem Unterschied zu warnen. :-)
Steve Byrnes
62

Obwohl wir frozensetin Python selten nützlich sind , gibt es seltsamerweise immer noch keine eingefrorene Zuordnung. Die Idee wurde in PEP 416 - Hinzufügen eines eingebauten Frozendict-Typs abgelehnt . Die Idee kann in Python 3.9 überarbeitet werden, siehe PEP 603 - Hinzufügen eines Frozenmap-Typs zu Sammlungen .

Also die Python 2-Lösung dafür:

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

Scheint immer noch etwas lahm zu sein:

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

In python3 haben Sie die Möglichkeit , diese :

from types import MappingProxyType

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

def foo(config=DEFAULTS):
    ...

Nun ist die Standardkonfiguration kann dynamisch aktualisiert werden, aber unveränderlich bleiben , wo Sie es möchten , indem Sie den Proxy statt , das um unveränderlich zu sein.

Änderungen im default_configwerden also DEFAULTSwie erwartet aktualisiert , aber Sie können nicht in das Mapping-Proxy-Objekt selbst schreiben.

Zugegeben, es ist nicht ganz dasselbe wie ein "unveränderliches, hashbares Diktat" - aber es ist ein anständiger Ersatz angesichts der gleichen Art von Anwendungsfällen, für die wir vielleicht ein Frozendict wollen.

wim
quelle
2
Gibt es einen bestimmten Grund, den Proxy in einer Modulvariablen zu speichern? Warum nicht einfach def foo(config=MappingProxyType({'a': 1})):? Ihr Beispiel erlaubt auch weiterhin globale Änderungen default_config.
jpmc26
Ich vermute auch, dass die doppelte Zuordnung config = default_config = {'a': 1}ein Tippfehler ist.
jpmc26
20

Angenommen, die Schlüssel und Werte des Wörterbuchs sind selbst unveränderlich (z. B. Zeichenfolgen), dann:

>>> 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
quelle
Dies ist eine gute, kanonische, unveränderliche Darstellung eines Diktats (abgesehen von einem wahnsinnigen Vergleichsverhalten, das die Art durcheinander bringt).
Mike Graham
6
@devin: stimmte voll und ganz zu, aber ich werde meinen Beitrag als Beispiel dafür stehen lassen, dass es oft einen noch besseren Weg gibt.
Msw
14
Noch besser wäre es, es in ein Frozenset zu legen, für das keine Schlüssel oder Werte definiert werden müssen.
Asmeurer
7
Nur ein Problem dabei: Sie haben kein Mapping mehr. Das wäre der springende Punkt, um das eingefrorene Diktat überhaupt zu haben.
Mad Physicist
2
Diese Methode ist wirklich nett, wenn man zu einem Diktat zurückkehrt. einfachdict(t)
Codythecoder
12

Es gibt keine fronzedict, aber Sie können diese verwenden MappingProxyType, die der Standardbibliothek mit Python 3.3 hinzugefügt wurde:

>>> 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
quelle
mit dem Vorbehalt:TypeError: can't pickle mappingproxy objects
Radu
Ich mag die Idee davon. Ich werde es versuchen.
Doug
10

Hier ist der Code, den ich verwendet habe. Ich habe Frozenset untergeordnet. Dies hat folgende Vorteile.

  1. Dies ist ein wirklich unveränderliches Objekt. Kein Vertrauen in das gute Verhalten zukünftiger Benutzer und Entwickler.
  2. Es ist einfach, zwischen einem normalen Wörterbuch und einem eingefrorenen Wörterbuch hin und her zu konvertieren. FrozenDict (orig_dict) -> eingefrorenes Wörterbuch. dict (Frozen_Dict) -> reguläres Diktat.

Update 21. Januar 2015: Der ursprüngliche Code, den ich 2014 veröffentlicht habe, verwendete eine for-Schleife, um einen passenden Schlüssel zu finden. Das war unglaublich langsam. Jetzt habe ich eine Implementierung zusammengestellt, die die Hashing-Funktionen von frozenset nutzt. Schlüssel-Wert-Paare werden in speziellen Containern gespeichert, in denen die Funktionen __hash__und __eq__nur auf dem Schlüssel basieren. Dieser Code wurde im Gegensatz zu dem, was ich hier im August 2014 gepostet habe, auch offiziell einem Unit-Test unterzogen.

MIT-Lizenz.

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
quelle
1
Beachten Sie, dass Sie es auch unter CC BY-SA 3.0 lizenziert haben, indem Sie es hier veröffentlichen. Zumindest ist das die vorherrschende Ansicht . Ich denke, die Rechtsgrundlage dafür ist die Zustimmung zu einigen AGB, als Sie sich zum ersten Mal angemeldet haben.
Evgeni Sergeev
1
Ich habe mir das Gehirn gebrochen und versucht, einen Weg zu finden, um den Schlüssel-Hash ohne Diktat nachzuschlagen. Die Neudefinition des Hashs von Item, um der Hash des Schlüssels zu sein, ist ein ordentlicher Hack!
Clacke
Leider ist die Laufzeit von diff(diff({key}))in der Größe des FrozenDict immer noch linear, während die reguläre Zugriffszeit für Diktate im Durchschnitt konstant ist.
Dennis
6

Ich denke jedes Mal an frozendict, wenn ich eine Funktion wie diese schreibe:

def do_something(blah, optional_dict_parm=None):
    if optional_dict_parm is None:
        optional_dict_parm = {}
Mark Visser
quelle
6
Jedes Mal, wenn ich einen Kommentar wie diesen sehe, bin ich sicher, dass ich es irgendwo vermasselt und {} als Standard festgelegt habe, und gehe zurück und schaue mir meinen kürzlich geschriebenen Code an.
Ryan Hiebert
1
Ja, es ist eine böse Sache, auf die jeder früher oder später stößt.
Mark Visser
8
Einfachere Formulierung:optional_dict_parm = optional_dict_parm or {}
Emmanuel
2
In diesem Fall können Sie als Standardwert für das Argument verwenden. types.MappingProxyType({})
GingerPlusPlus
@GingerPlusPlus könntest du das als Antwort aufschreiben?
Jonrsharpe
5

Sie können frozendictaus dem utilspiePaket verwenden als:

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

Gemäß dem Dokument :

frozendict (dict_obj) : Akzeptiert obj vom Typ dict und gibt ein hashbares und unveränderliches Dikt zurück

Moinuddin Quadri
quelle
5

Installieren Sie frozendict

pip install frozendict

Benutze es!

from frozendict import frozendict

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

Ja, dies ist meine zweite Antwort, aber es ist ein völlig anderer Ansatz. Die erste Implementierung war in reinem Python. Dieser ist in Cython. Wenn Sie wissen, wie man Cython-Module verwendet und kompiliert, ist dies genauso schnell wie ein normales Wörterbuch. Etwa 0,04 bis 0,06 Mikrosekunden, um einen einzelnen Wert abzurufen.

Dies ist die Datei "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']

Hier ist die Datei "setup.py"

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

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

Wenn Sie Cython installiert haben, speichern Sie die beiden oben genannten Dateien im selben Verzeichnis. Wechseln Sie in dieses Verzeichnis in der Befehlszeile.

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

Und du solltest fertig sein.

Steve Zelaznik
quelle
3

Der Hauptnachteil von namedtupleist, dass es spezifiziert werden muss, bevor es verwendet wird, so dass es für Einzelanwendungsfälle weniger bequem ist.

Es gibt jedoch eine praktische Problemumgehung, mit der viele solcher Fälle behandelt werden können. Angenommen, Sie möchten ein unveränderliches Äquivalent des folgenden Diktats haben:

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

Dies kann folgendermaßen emuliert werden:

from collections import namedtuple

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

Es ist sogar möglich, eine Hilfsfunktion zu schreiben, um dies zu automatisieren:

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

Natürlich funktioniert dies nur für flache Diktate, aber es sollte nicht zu schwierig sein, eine rekursive Version zu implementieren.

Berislav Lopac
quelle
1
Gleiches Problem wie bei der anderen Tupelantwort: Sie müssen getattr(fa, x)stattdessen fa[x]keine keysMethode an Ihren Fingerspitzen ausführen, und alle anderen Gründe, aus denen eine Zuordnung wünschenswert sein kann.
Mad Physicist
1

Unterklasse dict

Ich sehe dieses Muster in freier Wildbahn (Github) und wollte es erwähnen:

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

Anwendungsbeispiel:

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

Vorteile

  • Unterstützung für get(), keys(), items()( iteritems()auf py2) und alle Leckereien aus dictaus dem Kasten heraus, ohne sie explizit die Umsetzung
  • wird intern verwendet, dictwas Leistung bedeutet ( dictwird in CPython in c geschrieben)
  • elegant einfach und ohne schwarze Magie
  • isinstance(my_frozen_dict, dict)Gibt True zurück - obwohl Python die Enten-Typisierung bei vielen Paketen fördert isinstance(), können dadurch viele Optimierungen und Anpassungen eingespart werden

Nachteile

  • Jede Unterklasse kann dies überschreiben oder intern darauf zugreifen (Sie können etwas in Python nicht wirklich zu 100% schützen, Sie sollten Ihren Benutzern vertrauen und eine gute Dokumentation bereitstellen).
  • Wenn Sie Wert auf Geschwindigkeit legen, möchten Sie vielleicht __hash__etwas schneller machen.
ShmulikA
quelle
Ich habe einen Geschwindigkeitsvergleich in einem anderen Thread durchgeführt und es stellte sich heraus, dass das Überschreiben __setitem__und Erben dictim Vergleich zu vielen Alternativen wahnsinnig schnell ist.
Torxed
0

Eine weitere Option ist die MultiDictProxyKlasse aus dem multidictPaket.

Berislav Lopac
quelle
1
Leider ist es nicht hashbar.
Rominf
0

Ich musste an einem Punkt auf feste Schlüssel für etwas zugreifen, für etwas, das eine Art global konstante Sache war, und ich entschied mich für so etwas:

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

Verwenden Sie es wie

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

WARNUNG: Ich empfehle dies nicht für die meisten Anwendungsfälle, da es einige ziemlich schwerwiegende Kompromisse macht.

Adverbly
quelle
Das Folgende wäre ohne die Leistungseinbußen gleich leistungsfähig. Dies ist jedoch nur eine Vereinfachung der akzeptierten Antwort ... `` `Klasse FrozenDict: def __init __ (Selbst, Daten): self._data = Daten def __getitem __ (Selbst, Schlüssel): Rückgabe von self._data [Schlüssel]` ` `
Yuval
@ Yuval diese Antwort ist nicht gleichwertig. Für den Anfang ist die API anders, da sie Daten zum Initiieren benötigt. Dies bedeutet auch, dass es nicht mehr global zugänglich ist. Wenn _data mutiert ist, ändert sich außerdem Ihr Rückgabewert. Mir ist bewusst, dass es erhebliche Kompromisse gibt - wie gesagt, ich empfehle dies nicht für die meisten Anwendungsfälle.
Adverbly
-1

Wenn keine Unterstützung für Muttersprachen verfügbar ist, können Sie dies entweder selbst tun oder eine vorhandene Lösung verwenden. Glücklicherweise macht es Python ganz einfach, die Basisimplementierungen zu erweitern.

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
efreezy
quelle
Ihre frozen_dict Klasse ist nicht hashable
miracle173