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:

Mittwoch 7. Juli 2004, 23:16

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

Donnerstag 8. Juli 2004, 07:33

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:

Donnerstag 8. Juli 2004, 12:32

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:

Donnerstag 8. Juli 2004, 14:12

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

Donnerstag 8. Juli 2004, 17:44

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:

Mittwoch 14. Juli 2004, 02:41

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

Montag 27. September 2004, 12:22

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:

Montag 27. September 2004, 13:11

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
Moderator
Beiträge: 8461
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Mittwoch 8. Dezember 2004, 22:36

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:

Donnerstag 9. Dezember 2004, 00:06

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
Moderator
Beiträge: 8461
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Donnerstag 9. Dezember 2004, 07:45

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

Wenn ja, finde ich es völlig legitim!
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Donnerstag 9. Dezember 2004, 12:50

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 Modvoice
Benutzeravatar
jens
Moderator
Beiträge: 8461
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Donnerstag 9. Dezember 2004, 12:51

Achso, erst beim beenden... Pfui... Dann hab ihr absolut recht!
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Donnerstag 9. Dezember 2004, 12:58

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 Modvoice
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Donnerstag 9. Dezember 2004, 14:11

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