Einlesen einer Datei in ein Dictionary

Code-Stücke können hier veröffentlicht werden.
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hallo zusammen,

ich stand wiedermal vor dem Problem eine Datei, die aus Schlüssel/Werte-Paaren (getrennt durch ein "=") besteht in ein Dictionary einzulesen. Das Spezielle an der Datei ist, daß Werte, in Anführungszeichen eingeschlossen auch über mehrere Zeilen gehen können. Mit ConfigParser und Konsorten ist da kein Staat zu machen! Aber Python hat ja doch die Batterien schon drinn. Mit einer Regular Expression hab ich jetzt was gebastelt, das Ihr euch gerne mal anschaunen könnt.

Code: Alles auswählen

#!/usr/bin/env python

import sys, re

def file_to_dict(name):
    f = file(name)
    c = f.read()
    f.close()
    result = dict([(x[1],x[2].strip('"').strip())
                    for x in re.findall('((\w+)\s*=[ \t\f\v]*(("[^"]*")|(\S*))[^\n]*\n)',c)])
    return result

if len(sys.argv) == 2:
    print file_to_dict(sys.argv[1])
Hier gleich mal eine Beispieldatei:

Code: Alles auswählen

# Dies ist eine Kommentarzeile
option1 = "nix"

    option2 = viel
option3 = "
Hallo
Welt"
option4 = 4711 #mit kommentar dahinter
noch_eine_Option = 0 ; noch ein kommentar
leer = 
abg = "alpha beta gamma"
Das ganze ist auch sehr robust, wie ihr seht machen Leerzeichen und Kommentare sowie Leerzeilen keine Probleme.


Gruß

Dookie
Zuletzt geändert von Dookie am Mittwoch 28. Juli 2004, 21:50, insgesamt 2-mal geändert.
mawe
Python-Forum Veteran
Beiträge: 1209
Registriert: Montag 29. September 2003, 17:18
Wohnort: Purkersdorf (bei Wien [Austria])

Hi Dookie!

Gefällt mir! :D

Code: Alles auswählen

 print file_dict(sys.argv[1])
Da gehört print file_to_dict(sys.argv[1]) hin.

Hab mir überlegt, daß man das ganze noch flexibler machen könnte, wenn man der Funktion auch das Trennzeichen übergeben kann.

Code: Alles auswählen

import sys, re

def file_to_dict(name,sep):
    f = file(name)
    c = f.read()
    f.close()
    regex = '((\w+)\s*'+sep+'[ \t\f\v]*(("[^"]*")|(\S*))[^\n]*\n)'
    result = dict([(x[1],x[2].strip('"').strip()) for x in re.findall(regex,c)])
    return result

if len(sys.argv) == 2:
    # Trennzeichen soll ein Doppelpunkt sein
    print file_to_dict(sys.argv[1],':')
Jetzt kann man z.B. auch so eine Datei parsen:

Code: Alles auswählen

# Dies ist eine Kommentarzeile
option1 : "nix"

	option2 : viel
option3 : "
Hallo
Welt"
option4 : 4711 #mit kommentar dahinter
noch_eine_option : 0; noch ein kommentar
leer :
abg : "alpha beta gamma"
Gruß, mawe
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

mawe hat geschrieben:Hi Dookie!

Gefällt mir! :D

Code: Alles auswählen

 print file_dict(sys.argv[1])
Da gehört print file_to_dict(sys.argv[1]) hin.
Hi mawe,

habs geändert.
Das mit dem Seperator ist eine gute Idee.


Gruß

Dookie
Zuletzt geändert von Dookie am Mittwoch 14. Juli 2004, 02:35, insgesamt 1-mal geändert.
[code]#!/usr/bin/env python
import this[/code]
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

kleines update

Code: Alles auswählen

#!/usr/bin/env python

def file_to_dict(name, sep="="):
    f = file(name,'r')
    c = f.read()
    f.close()
    regex = '((\w+)\s*%s[ \t\f\v]*(("[^"]*")|(\S*))[^\n]*\n)' % sep
    result = dict([(x[1],x[2].strip('"').strip()) for x in re.findall(regex,c)])
    return result 

if len(sys.argv) == 2:
    print file_to_dict(sys.argv[1],"[:=]")
Jetzt ist "=" als Trennzeichen voreingestellt, mit "[:=]" z.B. kann sowohl ":" als auch "=" als Trennzeichen fungieren.


Gruß

Dookie
Zuletzt geändert von Dookie am Mittwoch 14. Juli 2004, 02:37, insgesamt 1-mal geändert.
mawe
Python-Forum Veteran
Beiträge: 1209
Registriert: Montag 29. September 2003, 17:18
Wohnort: Purkersdorf (bei Wien [Austria])

Hi!
Dookie hat geschrieben: Jetzt ist "=" als Trennzeichen voreingestellt, mit "[:=]" z.B. kann sowohl ":" als auch "=" als Trennzeichen fungieren.
Das ist natürlich genial, und

Code: Alles auswählen

regex = '((\w+)\s*%s[ \t\f\v]*(("[^"]*")|(\S*))[^\n]*\n)' % sep 
sieht auch eleganter aus als meine Version.

Code: Alles auswählen

def file_dict(name, sep="="): 
...
    print file_to_dict(sys.argv[1],"[:=]")
Dazu sag ich nichts mehr... :wink:

Gruß, mawe
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

hehe :oops:

jetzt passts aber auch oben mit _to_

und hier noch das Gegenstück:

Code: Alles auswählen

def dict_to_file(d, name, sep='='):
    items = d.items()
    items.sort()
    f = file(name, 'w')
    f.write("# dictionary written by %s\n\n" % sys.argv[0])
    for key, value in items:
        f.write('%s\t%s "%s"\n' % (key, sep, str(value)))
    f.close()
Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Stolzi
User
Beiträge: 155
Registriert: Mittwoch 18. August 2004, 15:44

Super das File to Dict Dookie! Danke dafür.
Für mich stellt sich nur noch folgende Frage:
Wenn ich nun keine besondere Datei zum einlesen habe wie du, sondern einfach nur nach einer Funktion suche die mir Einstellungen für mein Programm in einer Datei speichert und wieder ausliest sollte ich dann besser Pickle verwenden? Was würdest du wählen?
Danke
Stolzi
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hi Stolzi,

ich hab pickle noch nie verwendet :)
Es kommt halt stark auf die Daten an, die du Speichern willst. Bei einfachen Sachen würde ich das File_to_dict, wählen.
Wenn die Daten komplexer und/oder hierarchisch aufgebaut sind (verschachtelte Listen/Dictionaries) würd ich dann zu XML greiffen.
So bleiben die Daten unabhängig, also wenn Du Dein Programm mal z.B. in C umsetzen willst, kannst Du die gleichen Einstellungsdateien verwenden. Ausserdem sind diese Dateien auch von Menschen lesbar und anpassbar.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab mir dict_to_file() mal angesehen... Ich fand's doof, das ein Tabulator nicht immer alles auf einer Linie "geordnet" hat... Deshalb wird alles nach dem längsten Key (MaxLen) mittels ljust ausgerichtet:

Code: Alles auswählen

def dict_to_file( Dict, FileName, sep='=' ):
    items = Dict.items()
    items.sort()
    MaxLen = max( [len(key) for key in Dict.keys()] )
    f = file(FileName, 'w')
    f.write("# dictionary written by %s\n\n" % __file__)
    for key, value in items:
        f.write('%s %s "%s"\n' % (key.ljust(MaxLen), sep, str(value)))
    f.close()
Außerdem hab ich sys.argv[0] durch __file__ getauscht, dann braucht man theoretisch kein import os ...

Ach da fällt mir gerade was für file_to_dict ein:
kann man nicht das:

Code: Alles auswählen

    f = file( FileName,'r' )
    c = f.read()
    f.close()
kürzen in das:

Code: Alles auswählen

c = file( FileName,'r' ).read()
In der Variante erübrigt sich das close(), weil es automatisch ausgeführt wird, hab ich mal irgendwo aufgeschnappt...
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hi jens,
jens hat geschrieben:Ach da fällt mir gerade was für file_to_dict ein:
kann man nicht das:

Code: Alles auswählen

    f = file( FileName,'r' )
    c = f.read()
    f.close()
kürzen in das:

Code: Alles auswählen

c = file( FileName,'r' ).read()
In der Variante erübrigt sich das close(), weil es automatisch ausgeführt wird, hab ich mal irgendwo aufgeschnappt...
kann man, ich find es aber sauberer wenn man Resourcen die man belegt auch wieder explizit freigibt. Also zu jedem open oder file auch ein entsprechendes close setzt. Man gewöhnt sich sonst "Unarten" an, die in anderen Programmiersprachen zu oft schlimmen und schwer zu findenden Fehlern führen können. Auch wenn ich sonst gerne Code auf eine Zeile zusammenquetsche, bin ich in dem Fall für den Dreizeiler :)


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Aber wird denn jetzt bei der einzeiler-Variante automatisch ein .close() gemacht oder nicht???

Wenn ja, finde ich es völlig legitim!
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

jens hat geschrieben:Aber wird denn jetzt bei der einzeiler-Variante automatisch ein .close() gemacht oder nicht???
Ja, der Python Interpreter macht das beim Beenden für alle noch offenen file-like's. Aber ich stimme da Dookie zu dass es besser ist von selbst zu schließen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Achso, erst beim beenden... Pfui... Dann hab ihr absolut recht!
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

jens hat geschrieben:Achso, erst beim beenden... Pfui... Dann hab ihr absolut recht!
Oder vielleicht auch schon wenn der GC drüberläuft. Aber auf jeden Fall ist es besserer Programmierstil das selbst zu machen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Du kannst dir ja auch eine Funktion schreiben die den Inhalt einer Datei zurückliefert.

Code: Alles auswählen

def file_content(fname):
    f = open(fname, 'r')
    content = f.read()
    f.close()
    return content
Dann reicht im Programm ein inhalt = file_content("foo.bar").


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Ich habe das eigentlich immer mit eval gemacht:

Code: Alles auswählen

def file2dict(name):
    return eval(file(name, "r").read())
    
def dict2file(name, dct):
    file(name, "w").write(str(dct))
Aber weil man ja eval nicht verwenden soll (Warum eigentlich?) werde ich jetzt auch deine Methoder verwenden.
Allerdings hat das mit eval den Vorteil, dass keine Informationen verlohren gehen. Int bleibt int, list bleibt list.
TUFKAB – the user formerly known as blackbird
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Was passiert mit eval(), wenn jemand die Datei manipuliert und ein sys.exit() darin schreibt ;)
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

sys.exit() is ja noch ned schlimm, ein "import os; os.system('format c:')" unter windows reingeschmuggelt in die Datei und es kommt freude auf ;)


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

Hi Blackbird. Siehst ja, was man da schönes mit machen kann :wink: wenn du alternativ beliebige Datenstrukturen von Python abspeichern willst (in einem Dict) dann schau dir doch mal die Module shelve und pickle an, damit geht das genauso bequem aber viel sicherer als mit eval.

@Dookie: eval kann keine Statements ausführen... os muss schon importiert sein, damit es läuft... was es aber nicht annähernd besser macht, da dies oft genug der Fall ist.
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Milan hat geschrieben:@Dookie: eval kann keine Statements ausführen... os muss schon importiert sein, damit es läuft... was es aber nicht annähernd besser macht, da dies oft genug der Fall ist.
Da ich eval nie verwende, wusst ich das nicht, aber wie du schon sagst, ist os ja oft genug schon importiert.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Antworten