namedtuple, wie macht man es richtig?

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
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

bin erst vor Kurzem auf namedtuple von collections gestoßen, was ich vorher nicht kannte.

Bisher habe ich meine Namen für Listen, so erstellt und verwendet.
In diesem Beispiel, verwende ich extra nur Code (ohne Funktionen usw...), also bitte nicht dran stören! :wink:

Code: Alles auswählen

listname2names = {
    'kundenauswahl' : {
        'kunde' : 0,
        'filiale' : 1,
        'namen' : 2,
        'zusatz' : 3,
        'street' : 4,
        'plz' : 5,
        'ort' : 6
        },
    'kunden' : {
        'kunde' : 0,
        'filiale' : 1,
        'kundendefinition' : 2,
        'anrede1' : 3,
        'namen' : 4,
        'zusatz' : 5,
        'anrede2' : 6,
        'namen2' : 7,
        'info_namen' : 8,
        'street' : 9,
        'plz' : 10,
        'ort' : 11,
        'entfernung' : 12,
        'homepage' : 13,
        'ust_nr' : 14,
        'steuer_nr' : 15,
        'handelsregister' : 16,
        'handelsregister_nr' : 17,
        'kundenzeichen' : 18,
        'datum' : 19,
        }
    }
Ich arbeite fast nur mit Dictionarys. TXT-Dateien werden beim Laden in Dictś umgewandelt.
Hier ein Beispiel, wie ich obige Namenslisten einsetze.

Code: Alles auswählen

pool = {
    ('047110', '') : ('047110', '', '0', '', 'Test GmbH', '', '',
        'Test-Allee 22', '012345', 'Testhausen', '', '', '', '', '', '', 
        '', '', '', '2014.12.04'),
    ('088112', '') : ('088112', '', '0', '', 'Maier GmbH', '', '',
        'Abc-Allee 22', '098765', 'Cuxhausen', '', '', '', '', '', '', 
        '', '', '', '2014.12.04')}

listname = 'kunden'
list_check = listname2names.get(listname)
namen = list_check.get('namen')
for key in pool:
    b = pool[key]
    print(b[namen])
Nun komme ich zu namedtuple, was mich sehr interessiert und die Funktionsweise mir schon ein wenig bekannt ist.
Hier verwende ich die gleiche obige Namensliste, was wenn man nur mit namedtuple arbeitet, noch wesentlich einfacher aussehen würde.
Hier speise ich alle Listennamen in ein Dictionary ein und weise namedtuple zu.

Code: Alles auswählen

nd = dict()
for listnames in listname2names:
    pos2name = dict([(listname2names[listnames][names], names)
        for names in list(listname2names[listnames].keys())])
    names = [pos2name[i] for i in sorted(pos2name)]
    nd[listnames] = namedtuple(listnames, '{}'.format(' '.join(names)))

pool = {
    ('047110', '') : ('047110', '', '0', '', 'Test GmbH', '', '',
        'Test-Allee 22', '012345', 'Testhausen', '', '', '', '', '', '', 
        '', '', '', '2014.12.04'),
    ('088112', '') : ('088112', '', '0', '', 'Maier GmbH', '', '',
        'Abc-Allee 22', '098765', 'Cuxhausen', '', '', '', '', '', '', 
        '', '', '', '2014.12.04')}

listname = 'kunden'
var = 'listname = listname'
exec(var)
listname = nd.get(listname)
for key in pool:
    b = pool[key]
    x = listname(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8],
            b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17],
            b[18], b[19])
    print(x.namen)
Was mich stört, ist daß ich alle Spalten eines Datensatzes angeben muß, damit die Ausgabe mit namedtuple funktioniert.
Obwohl ich nur vieleicht den Namen, wie im Beispiel ausgeben möchte.
Also wenn ich eine Liste oder eine Tuple, wie hier verwende

Code: Alles auswählen

    x = listname(b)
funktioniert 'x.namen' nicht mehr.
Gibt es da einen Trick?

Freue mich über Euren Input und Eure Tips!

Grüße Nobuddy
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: Wenn man von einem Dictionary sowohl key als auch value braucht, gibt es die dict.items() Methode mit dem über beides gleichzeitig iterieren kann. Bei Dir reichen ja die Werte und dafür gibts dict.values().
Was soll das `exec` bewirken? Statt .get benutzt Du besser eckige Klammern.
Woher kommt pool? Sind da nicht schon die Elementnamen irgendwo mitgespeichert?

Code: Alles auswählen

kundenauswahl = namedtuple('kundenauswahl', ['kunde', 'filiale', 'namen', 'zusatz', 'street', 'plz', 'ort'])
kunden = namedtuple('kunden', ['kunde', 'filiale', 'kundendefinition', 'anrede1', 'namen', 'zusatz',
    'anrede2', 'namen2', 'info_namen', 'street', 'plz', 'ort', 'entfernung', 'homepage',
    'ust_nr', 'steuer_nr', 'handelsregister', 'handelsregister_nr', 'kundenzeichen', 'datum',])

pool = {
    ('047110', '') : ('047110', '', '0', '', 'Test GmbH', '', '',
        'Test-Allee 22', '012345', 'Testhausen', '', '', '', '', '', '',
        '', '', '', '2014.12.04'),
    ('088112', '') : ('088112', '', '0', '', 'Maier GmbH', '', '',
        'Abc-Allee 22', '098765', 'Cuxhausen', '', '', '', '', '', '',
        '', '', '', '2014.12.04')}

for data in pool.values():
    x = kunden(*data)
    print(x.namen)
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

Hallo Sirius3,

Danke für Deinen Input! :wink:

Zuerst habe ich das Prinzip gezeigt, wie ich momentan Namen und die Spaltennummer in einem Dict festlege.
Bezugnehmend auf namedtuple, habe ich das gleiche Dict '{listennamen : {namen : soaltennummer}}' verwendet, da bei Änderungen in meinem Code, ich nicht alles auf namedtuple umstellen kann.

Das gleiche Prinzip, nur auf namedtuple ausgelegt, sollte dann so aussehen '{listennamen : [namen1, namen2, ... usw], }'.
Der Vorteil ist, nicht jeden Listennamen einzeln zu verarbeiten 'kunden = namedtuple('kunden', ['kunden', 'filialen', ... usw.])', sondern über das zuvor erwähnte Dict.

'pool' war evtl. ein doofer Namen, habe das mal im nachstehenden Code in 'kundendict' und 'kundenauswahldict' geändert.
Ich weiß das 'dict' am Ende ist überflüssig, hilft mir aber zu wissen, ob es sich bei den geladenen Daten um ein Dict oder Liste handelt.
Normalerweise, werden bei mir beim Start meines Programmes, alle notwendigen Dateien in Dictionarys geladen.
'kundendict' und 'kundenauswahldict', stelle ich nur im Code ein, für eine bessere Übersicht.

Mit 'listname = 'kundenauswahl'', definiere ich den Listennamen, mit 'var = 'listname = listname'' treffe ich die Vorbereitung für 'exec(var), mit der ich eine Variable aus dem Listennamen des Dict erstelle. Danach weiße ich mit 'listname = nd.get(listname)' den entsprechenden namedtuple zu.

Code: Alles auswählen

listname2names = {
    'kundenauswahl' : ['kunde', 'filiale', 'namen', 'zusatz', 'street',
        'plz', 'ort']
        ,
    'kunden' : ['kunde', 'filiale', 'kundendefinition', 'anrede1',
        'namen', 'zusatz', 'anrede2', 'namen2', 'info_namen', 'street',
        'plz', 'ort', 'entfernung', 'homepage', 'ust_nr', 'steuer_nr',
        'handelsregister', 'handelsregister_nr', 'kundenzeichen',
        'datum']
    }

nd = dict()
for listnames in listname2names:
    nd[listnames] = namedtuple(listnames, '{}'.format(
        ' '.join(listname2names[listnames])))

kundenauswahldict = {
    ('047110', '') : ('047110', '', 'Test GmbH', '', 'Test-Allee 22',
        '012345', 'Testhausen'),
    ('088112', '') : ('088112', '', 'Maier GmbH', '', 'Abc-Allee 22',
        '098765', 'Cuxhausen')}

listname = 'kundenauswahl'
var = 'listname = listname'
exec(var)
listname = nd.get(listname)
for data in kundenauswahldict.values():
    x = listname(*data)
    print(x.kunde, x.namen)

kundendict = {
    ('047110', '') : ('047110', '', '0', '', 'Test GmbH', '', '',
        'Test-Allee 22', '012345', 'Testhausen', '', '', '', '', '', '', 
        '', '', '', '2014.12.04'),
    ('088112', '') : ('088112', '', '0', '', 'Maier GmbH', '', '',
        'Abc-Allee 22', '098765', 'Cuxhausen', '', '', '', '', '', '', 
        '', '', '', '2014.12.04')}

listname = 'kunden'
var = 'listname = listname'
exec(var)
listname = nd.get(listname)
for data in kundendict.values():
    x = listname(*data)
    print(x.namen, x.plz, x.ort)
Grüße Nobuddy
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: neben der Tatsache, dass man `exec` nie benutzen sollte, was machen jetzt die beiden Zeilen 24 und 25 konkret. Dass Du glaubst, dass sie das machen, was Du da geschrieben hast, ist mir schon klar. Aber Variablennamen werden nicht erstellt, die sind einfach da.

Und nochmal die Frage: Woher kommen denn die pool-Daten?
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

@Sirius3, habe das mit 'exec' und 'Zeilen 24 und 25' jetzt verstanden. Ist absolut unnötig, denn ohne dies funktioniert es genauso.
Danke für den kleinen Schubser! :wink:

Jetzt zu den 'pool-Dateien'.
Da ich NICHT mit einer Datenbank arbeite, sind alle meine Daten-Dateien TXT-Dateien im csv-Format erstellt.
Beim Start meines Programmes, werden alle diese TXT-Dateien in Dicts umgewandelt und in ein Dictionary gepackt und geladen.
So habe ich schnellen Zugriff auf meine Daten.
Die 'pool-Datei' habe ich eigentlich nur zum besseren Verständnis dargestellt.
Ich wollte Dich da nicht verwirren.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: Wenn das csv-Dateien sind, dann gibt es ja schon einen DictReader, der den Header der csv-Datei nimmt, um die Feldnamen zu ermitteln. Wenn Du statt dessen lieber NamedTuple haben willst, läßt sich das auch leicht umschreiben. Siehe z.B. hier: http://stackoverflow.com/questions/1676 ... dictreader
Das nachträgliche Zusammenflicken von Werten und Spaltennamen ist höchst fehleranfällig und umständlich.
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

@Sirius3,
ich habe mich da wohl falsch ausgedrückt.
Es sind keine csv-Dateien, sondern nur TAB-getrennte Textdateien.

Meine Beispieldatei kunden, würde so aussehen:
  • 047110 Test GmbH Test-Allee 22 012345 Testhausen
    088112 Maier GmbH Abc-Allee 22 098765 Cuxhausen
Da ist kein Spaltenkopf mit Feldnamen dabei, wenn ich das mit Header richtig verstehe.
Datensätze werden neu erstellt, bearbeitet und auch gelöscht. Zusätzlich kommt noch eine Sortierung beim Abspeichern hinzu.
Der Spaltenkopf, wäre dann irgendwo in den Datenzeilen zu finden und nicht an oberster Stelle.
Vielleicht hast Du mir da eine Lösung?

Die Spaltennamen für die Listen/Dicts sind fest in einem separaten Modul definiert.
Dort wird auch gleichzeitig der Ort der Dateien definiert.
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

Statt dies von meiner letzten Code-Ausgabe:

Code: Alles auswählen

nd = dict()
for listnames in listname2names:
    nd[listnames] = namedtuple(listnames, '{}'.format(
        ' '.join(listname2names[listnames])))
, läßt sich das Dank Deiner Hilfe, so vereinfachen:

Code: Alles auswählen

nd = dict([(listnames[0], namedtuple(*listnames))
    for listnames in listname2names.items()])
Und statt:

Code: Alles auswählen

listname = 'kundenauswahl'
listname = nd.get(listname)
for data in kundenauswahldict.values():
    x = listname(*data)
    print(x.kunde, x.namen)
,dies hier:

Code: Alles auswählen

for data in kundenauswahldict.values():
    x = nd.get('kundenauswahl')(*data)
    print(x.kunde, x.namen)
Zuletzt geändert von Nobuddy am Freitag 5. Dezember 2014, 19:19, insgesamt 1-mal geändert.
Benutzeravatar
ngulam
User
Beiträge: 35
Registriert: Freitag 18. Oktober 2013, 11:03

Nobuddy hat geschrieben:Es sind keine csv-Dateien, sondern nur TAB-getrennte Textdateien.
Auch mit diesem Trennzeichen sind es CSV-Dateien (hier: character separated values), die mit dem csv-Modul bearbeitet werden können.
งูหลาม
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: Wenn Du Dir das Dateiformat selbst ausgedacht hast, dann kannst Du einfach so eine Kopfzeile hinzufügen. Irgendwo anderswo etwas fest zu definieren macht irgendwann nur Probleme. Stell Dir vor, Du brauchst man noch Zusatzspalten, dann mußt Du Dich daran erinnern, wo überall Du etwas ändern mußt, damit Dein Programm weiter läuft.
Beim Speichern mußt Du doch sowieso immer die ganze Datei schreiben und die erste Zeile ist eben die Kopfzeile. Wie soll die irgendwo dazwischenrutschen?
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

@ngulam, dann sind es doch csv-Dateien, Danke! :wink:

@Sirius3, mit Deinen Argumenten hast Du Recht! :wink:
Die einzelnen Dateiformate habe ich selbst definiert und liegen in einem zentralen Modul.
Richtig ist auch, bei Änderung von Spalten in einzelnen Dateien, ist auch die Aktualisierung im Code notwendig.

Manche Dictonarys, werden aus Inhalten anderer, bzw. mehreren Dictonarys erstellt oder aktualisiert.
Beim Beenden des Programms, werden die Inhalte aus den Dictonarys sortiert und dann in die Dateien geschrieben.

Manche Dictionarys erhalten nicht den ganzen Inhalt in Value, sondern ein Teil steht im Key und ein Teil in Value.
Dementsprechend wird beim Zurückführen in die Datei, der Inhalt aus Key und Value zusammengestellt.

So eine Kofzeile, läßt sich ja nicht fest verankern, oder?
Ich müßte beim Laden der Dateien, ab Zeile 1 die Daten laden und beim Speichern der in Inhalte in die Dateien, zuerst die Datei mit der Kopfzeile erstellen und dann die Inhalte anfügen.
Vielleicht sehe ich das momentan noch zu kompliziert und würde mich daher sehr über Eure Hilfe zur Lösung freuen! :wink:

Ich weiß Fehler aus der Vergangenheit, sind nicht immer leicht zu beheben, bemühe mich aber dies doch zu tun!

Grüße Nobuddy
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

nach einigen Überlegungen, habe ich mich dazu entschlossen, die Spaltennamen der Listen nicht in die csv-Dateien ein zupflegen.
Momentan sehe ich darin keinen Vorteil.

Die Definition der Dateinamen mit Pfadangabe und die Listen mit Listen- und Spaltennamen, sind in einem Modul vorhanden.
Das Erstellen neuer Dateien und Listen, sowie Änderungen werden hier ausgeführt.
Spaltennamen habe ich so definiert, daß diese in den verschiedenen Listen wiederzufinden sind.
Wird ein Spaltennamen geändert, muß dieser auch im restlichen Code des Programms editiert werden.

Eine Frage habe ich zu namedtuple noch, für die ich bisher keine Lösung gefunden habe.
Ist es möglich aus namedtuple, auch die Position eines einzelnen Namens herauszufinden?
Beispiel:

Code: Alles auswählen

names = namedtuple('kundenauswahl', ['kunde', 'filiale', 'namen', 'zusatz', 'street',
        'plz', 'ort'])
Wie kann ich jetzt mit 'names' als Ausgangspunkt, die Spaltenposition von z.B. 'zusatz' finden?

Grüße Nobuddy
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

Zur Bestimmung der Spaltennumer aus namedtuple, habe ich bisher zwei Möglichkeiten aus der Python Dokumentation gefunden.

Code: Alles auswählen

for number, field in enumerate(names._fields):
    if field == 'zusatz':
        print(number)
und

Code: Alles auswählen

number = getattr(names._make(range(len(names._fields))), 'zusatz')
print(number)
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Wie sieht denn dein Anwendungsfall aus, dass du die Spaltennummern zu den Namen brauchst?
Nobuddy hat geschrieben:Spaltennamen habe ich so definiert, daß diese in den verschiedenen Listen wiederzufinden sind.
Wird ein Spaltennamen geändert, muß dieser auch im restlichen Code des Programms editiert werden.
Das heißt doch aber nicht, dass irgendwo Listen mit Spaltennamen rumfliegen, die beim Programmstart dynamisch zusammengebaut werden, oder?

Da du von "verschiedenen Listen" sprichst: Warum verwendest du keine Datenbank? Dein Problem schreit ja gerade danach.
Das Leben ist wie ein Tennisball.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: die Spaltennummer bekommst Du mit

Code: Alles auswählen

number = names._fields.index('zusatz')
Normalerweise braucht man so eine Funktion nicht, weil der Sinn von NamedTuples ja gerade ist, dass man keine Indexes zu wissen braucht.
csv-Dateien ohne Header sind einfach nur kaputt.
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

die Spaltennummer benötige ich momentan noch für diverse Arbeiten, bis ich alles umgestellt habe.
Bei ca. 10.000 Zeilen Code und einige Zusatzmodule, wird das noch ein Weilchen dauern. :wink:

Prinzipiell, habe ich mich gegen eine Datenbank entschieden.
Außer mich noch mit zusätzlicher Datenbankprogrammierung zu beschäftigen müssen, sind mir TXT-Dateien im csv-Format einfach lieber, da sich solche Dateien einfach öffnen lassen und ein direkter visueller zugriff immer möglich ist, was bei einer Datenbank nicht der Fall ist.

Wie schon gesagt, werden alle Listen mit deren Spaltennamen zentral verwaltet.
Werden z.B. bestimmte Spalten in einer Liste z.B. Auftragsdaten aktualisiert, so muß ich ja im Code die Spaltennamen bzw. -nummer angeben, die aktualisiert werden sollen.

Der Tipp mit index ist super und nicht so umständlich, wie meine Code.
Danke Sirius3 :wink:
Nobuddy
User
Beiträge: 996
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

wünsche Euch ein gutes neues Jahr! :wink:

Das mit den namestuple, funktioniert inzwischen echt super und vereinfacht das Leben ungemein.
Von den 10.000 Zeilen Code, habe ich schon ein beachtliches Stück geschafft und konnte dadurch auch die Codezeilen verkürzen. :)
Was mir hier zugute kam, war daß ich schon lang zuvor, die Steuerung von Listen, Listennamen und Spaltennamen zentralisiert hatte und die Spaltennamen in den Listen vereinheitlicht hatte.

Grüße Nobuddy
Antworten