Dictionary vor Veränderung schützen! (kein CBR sondern CBV)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Apotekarnes
User
Beiträge: 18
Registriert: Mittwoch 21. September 2011, 10:02

Hallo!

Mein Programm benutzt Dictionaries von mehreren Gigabytes im Hauptspeicher (Großrechner). Diese werden teilweise an Funktionen übergeben. In den Funktionen sollen (lokal!) Modifikationen an den übergebenen Dictionaries durchgeführt, die sich jedoch leider direkt nach außerhalb der Funktionen durchschlagen. Klar: Call by reference, da mutabler Datentyp!
Wie kann ich die Dictionaries vor Veränderung durch die Funktionen schützen?

Sprich - ich will innerhalb der Funktion das Dictionary nur als lokale Variable behandeln!

Ich kenne bereits den Trick für Listen innerhalb der Funktionen mit templist = oldlist[:].

Gibt es sowas auch für Dictionaries?

Ich habe es auch schon mit dem Modul "copy" und dann templist = copy.deepcopy(oldlist) probiert. Das funktioniert, dauert aber leider unglaublich lange bei Dictionaries von mehreren GB Größe!

Es wäre also eine Schutzfunktion wünschenswert, bei der nicht die gesamte Variable in gesamter Größe im Hauptspeicher verdoppelt / kopiert werden muss.
Die Dictionaries werden durch Auslesen aus riesigen Dateien erzeugt. Durch die Übergabe schon ausgelesener Daten an die Funktionen will ich ein neues zeitaufwändiges Auslesen der Dateien vermeiden. Leider verliere ich diese gesparte Zeit wieder beim Anfertigen der Arbeitskopie.

Irgendwelche Ideen? Bräuchte da dringend Hilfe.

Danke,
Apotekarnes
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Nein Dictionaries kann man nicht auf read-only schalten, selbst wenn wuerde das (im naiven Fall) doch wieder in Kopien resultieren und die fallen deiner Analyse zufolge ja weg.

Beschreibe doch mal wie die Zugriffs-/Modifikationsmuster aussehen.

Wenn du z.B. nur eine lokale "Modifikation" brauchst, wuerde ich ein 2-stufiges Dictionary bauen, das aus einem Lokalen Teil und einem globalen/default Teil besteht. Saemtliche Modifikationen finden im Lokalen Dictionary statt und Lookups in beiden, wobei das Lokale bevorzugt wird.
Apotekarnes
User
Beiträge: 18
Registriert: Mittwoch 21. September 2011, 10:02

Die Modifikation wäre einfach die Veränderung eines Values bei einem bestimmten Key.
Es können aber auch tausende Einträge verändert werden. Das muss die entsprechende Funktion feststellen.

Eine Aufteilung in global / lokal wird entweder bedeuten, dass ich den gesamten Inhalt des urspründlichen Dictionaries verdoppeln muss, was wieder in einer Verdopplung des Speicherbedarfs resultiert oder ich deklariere genau die zu verändernden Bereiche als lokal. Das geht auch nicht, da ich diese Bereiche nicht von außerhalb der Funktion kenne und wenn ich sie kennen würde, so würde ich trotzdem nur Veränderungen am Dictionary innerhalb der Funktion erlauben wollen.


Man kommt wohl nicht drum herum, irgendeine Form von Kopie der Daten anzufertigen. Da ich diese Kopie nicht außerhalb, sondern nur innerhalb der Funktionen haben will (zusätzlicher Speicher wird dann nur während des Funktionsdurchlaufs benötigt), muss ich sie zu Beginn der Funktion erstellen.
copy.deepcopy() ist aber VIEL zu langsam.
Ist es geschickter, das Originaldictionary Key für Key durchzugehen und die Einträge in ein neues Dictionary abzuschreiben?
Laufzeit?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Nein du verstehst mich falsch. Du deklarierst nichts, sondern Kapselst dein Dictionary in eine Klasse, die ein weiteres Dictionary enthaelt, eben das lokale.

Speicherplatz belegst du nur fuer neue Werte mehr, das hast du aber sowieso, denn die Paare, die du behalten willst, musst du ja immernoch speichern.

Hier eine kurze Skizze in Code:

Code: Alles auswählen

class TwoLevelDictionary(object):
    def __init__(self, default_dict):
        self.default = default_dict
        self.local = {}

    def __setitem__(self, key, value):
        self.local[key] = value

    def __getitem__(self, key):
        if key in self.local:
            return self.local[key]
        return self.default[key]
Das default_dict wird hierbei nie veraendert. Damit du das ganze auch wirklich als dict benutzen kannst, brauchst du evtl noch ein bisschen mehr Arbeit, aber das kommt auf deine Anforderungen an.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Noch als kleine Ergänzung:

Code: Alles auswählen

    def __getitem__(self, key):
        try:
            return self.local[key]
        except KeyError:
            return self.default[key]
Das Leben ist wie ein Tennisball.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Hier noch eine Variante für __getitem__():

Code: Alles auswählen

def __getitem__(self, key):
    return self.local.get(key, self.default[key])
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

kbr hat geschrieben:Hier noch eine Variante für __getitem__():

Code: Alles auswählen

def __getitem__(self, key):
    return self.local.get(key, self.default[key])
Davon ist abzuraten. `self.default[key]` wird _immer_ ausgewertet, auch wenn es den Key in local gibt. Das fuehrt zu einem Fehler, wenn der Key neu in local ist und nicht ueberschrieben wurde.

Code: Alles auswählen

In [14]: %paste
class TwoLevelDictionary(object):
    def __init__(self, default_dict):
        self.default = default_dict
        self.local = {}

    def __setitem__(self, key, value):
        self.local[key] = value

    def __getitem__(self, key):
        if key in self.local:
            return self.local[key]
        return self.default[key]
## -- End pasted text --

In [15]: d = TwoLevelDictionary({})

In [16]: d['foo']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/home/cofi/<ipython-input-16-1af12a7c631f> in <module>()
----> 1 d['foo']

/home/cofi/<ipython-input-14-b4cc57198c76> in __getitem__(self, key)
     10         if key in self.local:
     11             return self.local[key]
---> 12         return self.default[key]

KeyError: 'foo'

In [17]: d['foo'] = 2

In [18]: TwoLevelDictionary.__getitem__ = lambda self, key: self.local.get(key, self.default[key])

In [19]: d['foo']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/home/cofi/<ipython-input-19-1af12a7c631f> in <module>()
----> 1 d['foo']

/home/cofi/<ipython-input-18-e0ea848b36db> in <lambda>(self, key)
----> 1 TwoLevelDictionary.__getitem__ = lambda self, key: self.local.get(key, self.default[key])

KeyError: 'foo'

In [20]: d.local
Out[20]: {'foo': 2}
Apotekarnes
User
Beiträge: 18
Registriert: Mittwoch 21. September 2011, 10:02

Vielen Dank, das sind wirklich konstruktive Vorschläge!

Wenn ich mich nicht irre, hat diese Datenstruktur auch sämtliche Funktionen des ursprünglichen Dictionaries.
Sehr gut!
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

cofi hat geschrieben:`self.default[key]` wird _immer_ ausgewertet, auch wenn es den Key in local gibt.
Danke, stimmt, kann dann einen KeyError auslösen - und der User wundert sich. Wieder ein Argument mehr für TDD.
Zuletzt geändert von kbr am Donnerstag 14. Juni 2012, 12:12, insgesamt 1-mal geändert.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Es gibt doch seit Python 2.7 `dictionary-views`, wären die nichts?

http://docs.python.org/dev/whatsnew/2.7 ... nary-views
http://docs.python.org/library/stdtypes.html#dict

Edit: Anscheinend nicht, ich dachte man kann `views` auf einzelne Key-Value Paare haben.
the more they change the more they stay the same
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Apotekarnes hat geschrieben:Wenn ich mich nicht irre, hat diese Datenstruktur auch sämtliche Funktionen des ursprünglichen Dictionaries.
Nein, du irrst dich. Iterieren ueber das dictionary geht beispielsweise nicht, ueberpruefen ob ein Key enthalten ist ebenfalls nicht, usw.
Aber ob das wichtig ist, haengt von deinen Anforderungen ab, wenn du sowas brauchst, schau dir `collections.MutableMapping` an und erbe davon.

@kbr: Ich weiss nicht ob die fehlende Kenntnis von Python Semantik wirklich ein gueltiges oder ernstzunehmendes Argument fuer TDD ist ;)

@Dav1d: Views koennten ein Teil der Loesung sein, aber am Ende hat man ja doch noch das Problem, dass man gar kein Immutable Dict haben will, sondern ein selektiv veraenderbares. D.h. man braeuchte auch wieder eine Moeglichkeit beides zu kombinieren und landet dann IMO auch wieder bei einem Proxy wie ich ihn skizziert habe. Mit views koennte man aber IMO sicher gehn, dass die Funktionen das urspruengliche Dict auf keinen Fall aendern, aber ich glaube man bekommt keinen view, der sich tatsaechlich dict-like verhaelt.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

EyDu hat geschrieben:Noch als kleine Ergänzung:

Code: Alles auswählen

    def __getitem__(self, key):
        try:
            return self.local[key]
        except KeyError:
            return self.default[key]
Das macht eigentlich nur dann Sinn, wenn verhältnismäßig viele Werte lokal vorgehalten werden, da das Heraussuchen des Wertes (bzw des Schlüsselnamens als Hashwert) in dem Fall nur einmal stattfände. Andernfalls erzeugt diese Variante jedoch relativ hohe (Performance-)Kosten für das jeweilige Werfen der Ausnahme, auf die man dann ja mit dem globalen Wert reagieren würde.

Man könnte halt beide Möglichkeiten mal in der Praxis testen (sofern der Unterschied überhaupt eine Rolle spielen sollte). Es kommt irgendwo auch darauf an, was die Funktionen genau mit dem übergebenen `dict` veranstalten. Denn berechtigt ist die Annahme, dass lokal eingefügte Werte auch lokal benötigt werden, ja durchaus. Fragt sich halt nur, in welchem Verhältnis diese zu der Anzahl an benötigten Original-Werten stehen.
Antworten