Textdatei auslesen und in Array speichern

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.
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

Hallo Leute,
ich bin neu in der Programmierung von Python 2.7... ein NOOB also ;-)

Ich habe eine Textdatei mit folgendem Schema:

['a1', 'a2', ['langer text']]
['b1', 'b2', ['langer text']]
['c1', 'c2', ['langer text']]
['d1', 'd2', ['langer text']]
....

Auf diese Daten muss ich am besten in einem zweidimensionalen array (für eine For Schleife) zugreifen.
Der Inhalt im array soll ohne Hochkomma und ohne klammern sein.

Wäre toll wenn mir jemand einen Tipp geben kann.

Danke im voraus.

Thomas
BlackJack

@TheMerchant: Wie ist denn dieses Textformat zustande gekommen? Falls das literale Python-Datenstrukturen sind, könnte man die mit `ast.literal_eval()` *sicher* parsen. Und man sollte in Zukunft nicht solche Dateien erstellen sondern sich vorher mehr Gedanken über ein vernünftiges Datenformat und die Weiterverwendung machen.
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

Hallo BlackJack,

danke für die Schnelle Antwort.
Die Daten werden von einem weiteren Pythonscript aus einer Datei und aus dem Internet via FTP "zusammengestrickt".

Die Datei ist eine cfg zum auslesen eines FTP Servser im Netz:

Hier der Inhalt der cfg:
[Station]
metarstations =("EDDK","EDDL","EDDM")
[Stationname]
metarstationsname =("Koeln/Bonn","Duesseldorf","Muenchen")

Das Ergebnis in der Textdatei ist dann folgende:
['EDDK', 'Koeln/Bonn', ['EDDK 100320Z 12004KT 9999 FEW030 BKN040 08/08 Q1010 NOSIG']]
['EDDL', 'Duesseldorf', ['EDDL 100320Z 16007KT 8000 -RA FEW006 BKN036 09/08 Q1009 NOSIG']]
['EDDM', 'Muenchen', ['EDDM 100320Z VRB01KT 3500 BR SCT002 OVC003 06/06 Q1011 BECMG 23005KT']]

Jeweils die erste und die zweite Position ist aus der cfg und die dritte ist aus dem Internet.
BlackJack

@TheMerchant: Wie gesagt, Python-Datenstrukturen einfach so in einer Textdatei zu speichern in dem man sie in Zeichenketten umwandelt ist keine gute Idee. Man sollte sich Gedanken um ein Datenformat machen das man nicht als Python-Quelltext parsen muss. Bei diesen Daten scheint mir eine CSV noch machbar wenn im dritten Element in der Liste grundsätzlich immer nur *ein* Element steckt (warum ist das dann überhaupt eine Liste?). Ansonsten eignet sich das JSON-Format für verschachteltere Daten. Für beides bietet die Standardbibliothek Module.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Hier sind zwei Möglichkeiten

Code: Alles auswählen

# mit eval, wenn Du Dir sicher bist, dass in text.txt nichts Boeses steht
text = []
for line_str in open("text.txt").readlines():
    line = eval(line_str)
    line[2] = line[2][0]
    text.append(line)

# mit replace, funktioniert nicht, wenn in den Strings selbst "[" oder "]" steht
text = []
for line_str in open("text.txt").readlines():
    line = line_str.split(",")
    line = [l.replace("[","").replace("]","",).replace("'","").strip() for l in line]
    text.append(line)

for i in range(len(text)):
    for j in range(3):
        print i, j, text[i][j]
Ausgabe

Code: Alles auswählen

0 0 a1
0 1 a2
0 2 langer text
1 0 b1
1 1 b2
1 2 langer text
2 0 c1
2 1 c2
2 2 langer text
3 0 d1
3 1 d2
3 2 langer text
In text.txt steht bei mir

Code: Alles auswählen

['a1', 'a2', ['langer text']]
['b1', 'b2', ['langer text']]
['c1', 'c2', ['langer text']]
['d1', 'd2', ['langer text']]
Wenn Du Daten als Python-Code speicherst, dann kannst Du es auch gleich so machen, dass Du die Daten ohne Schleife einlesen kannst. Da ja Python selbst einen Parser für Python Code hat, brauchst Du diesen ja nicht nochmal zu entwickeln:

Code: Alles auswählen

data=[
['a1', 'a2', ['langer text']],
['b1', 'b2', ['langer text']],
['c1', 'c2', ['langer text']],
['d1', 'd2', ['langer text']]
]
Zuletzt geändert von MagBen am Montag 10. November 2014, 12:36, insgesamt 1-mal geändert.
a fool with a tool is still a fool, www.magben.de, YouTube
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

@BlackJack:
Wie anfangs gesagt bin ich ein NOOB und für alle Vorschlage zur Verbesserung dankbar.
Warum dies eine Liste ist kann ich nicht sagen.
Kannst Du mir sagen wie man das am besten bewerkstelligt.
Die Randbedigungen kennst du ja.

hier mal ein LIVE ftp String:

ftp://tgftp.nws.noaa.gov/data/observati ... s/EDDK.TXT

Die erste Zeile schneide ich weg und die zweite brauche ich.
Diese Daten ändern sich alle 30 Minuten. Ich habe einen Cron der die Daten in eine Datei speichen soll (und auch macht). z. Zeit in eine Textdatei.

Wenn Du mir sagen (zeigen) kannst wie das besser ist wäre ich dir dankbar.
BlackJack

@MagBen: Wie kann man nur `eval()` vorschlagen nach dem das *sichere* `ast.literal_eval()` schon erwähnt wurde. :roll: Womit auch das nicht wirklich robuste Stringgefummel für die Tonne ist.

@TheMerchant: Warum das eine Liste ist musst Du aber sagen können wenn der Code von Dir ist. Du steckst das ja schliesslich irgendwo in Deinem Code in eine Liste. Von selbst passiert das nicht.

Wenn Du die erste Zeile verwirfst, geht dann nicht wichtige Information verloren, nämlich auf welchen Zeitpunkt sich die Daten in der zweiten Zeile beziehen?

Da die Datei laufend erweitert wird kommt JSON nicht wirklich in Frage und da es pro Datensatz keine verschachtelten Daten sind, reicht CSV aus. Wie gesagt gibt es dafür ein Modul in der Standardbibliothek. Das hat den Namen `csv`.
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

@BlackJack:

Nein es gehen keine wichtigen Informationen verloren.
2014/11/10 11:50
EDDK 101150Z 16008KT 9999 FEW030 BKN055 12/09 Q1010 NOSIG

Nach EDDK steht auch das Datum und die Uhrzeit ;-) 101150Z (10. Tag im Monat 11:50 Zulu)

Hier der Teil der "die Liste" anlegt (denke ich):

Code: Alles auswählen

def METAR_download(): 
  metarconfig()
  del metarLCD[:]
  global text
  meinftp = ftplib.FTP('tgftp.nws.noaa.gov','','',90) #Bei der Erzeugung einer FTP-Instanz kann
  meinftp.login("","")
  directory = '/data/observations/metar/stations/' #ftp-Hauptverzeichnis  
  meinftp.cwd(directory) #Wir nutzen das Hauptverzeichnis des ftp-Servers. 
  
  for i in range(0, len(metarstations)):
    filename = metarstations[i]+".TXT"#print filename
    meinftp.retrlines('RETR '+filename, METARDaten)
    text = text.split("\n")
    del text [0]
    del text [1]
    metarLCD.append(metarstations[i])
    metarLCD.append(metarstationsname[i])
    metarLCD.append(text)    
    text=""

  meinftp.quit() #"hoefliches" Trennen meinerseits der ftp-Verbindung

def logTOfile():
  doc = open ('/pfad/zur/textdatei', 'a')
  for i in range(0,len(metarstations)):
    x = i*3
    nachricht = str(metarLCD[0+x:3+x])
    doc.write(strftime(nachricht +'\n'))
  doc.close()


METAR_download()
logTOfile()
BlackJack

@TheMerchant: Das sollte man komplett neuschreiben. Mit echten Funktionen statt Codeabschnitten die Namen haben und alles über globale Datenstrukturen abwickeln, mit vernünftigen Datenstrukturen wo zusammengehörige Daten weder über ”parallele” Listen verteilt sind noch in einer Liste Elemente hintereinander stehen die eigentlich zusammengefasst gehören. Und wahrscheinlich auch komplett ohne ``del``-Anweisung. Die braucht man eher selten in Python.

Und was Namenskonventionen und Quelltextformatierung angeht, lohnt sich ein Blick in den Style Guide for Python Code.

Edit: Ist das Format der Konfigurationsdatei auch von Dir? Das hat ja auch diese äusserst ungünstige Trennung der Werte in parallele Datenstrukturen:

Code: Alles auswählen

[Station]
metarstations =("EDDK","EDDL","EDDM")
[Stationname]
metarstationsname =("Koeln/Bonn","Duesseldorf","Muenchen")
Womit parst Du das denn? Also speziell die Werte zu einem Schlüssel?

Sinnvoller und weniger Fehleranfällig wäre es doch die Stationskürzel als Schlüssel zu verwenden und auf den Namen abzubilden. Oder sind die Kürzel nicht eindeutig?

Code: Alles auswählen

[Stations]
EDDK=Koeln/Bonn
EDDL=Duesseldorf
EDDM=Muenchen
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

@BlackJack:
man oh man das als NOOB... bin kein Programmierer... bin ein "zusammenstricker"

Bin froh das es einiger maßen läuft und Du sagt was von Programmierkonventionen und das ich alles neu machen soll... na Danke.

Nein, nein finde ich ja gut. Stillstand ist Rückschritt.
Aber bevor man Fliegen kann sollte man (ich) ersteinmal versuchen zu "krabbeln".
Mir bringt es auch nichts wenn ich den Code bekomme und ihn dann nicht verstehe.

O.k. Also du meinst ich sollte das ales als csv machen.
Werde ich mir mal anschauen.

Wenn Du möchtest (und weiter Zeit in mich investieren möchtest) kann ich dir den Original Quelltest mal per PM senden.

Danke Dir für die Hilfe
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

@BlackJack:
Also die Dateistrucktur habe ich mir ausgedacht.
metarstation brauche ich für den ftp
metarstationname ist nur zum anzeigen.

Code: Alles auswählen

    [Station]
    metarstations =("EDDK","EDDL","EDDM")
    [Stationname]
    metarstationsname =("Koeln/Bonn","Duesseldorf","Muenchen")
BlackJack

@TheMerchant: Ja aber warum ist die Konfigurationsdatei so ungünstig und fehleranfällig und nicht als Abbildung Kürzel zu Name?

Das Problem das ich mit dem aktuellen Quelltext sehe ist, dass das nicht mal krabbeln ist. Da hängt zu viel irgendwie zusammen was eigentlich ordentlich getrennte Schritte sein sollten die man einzeln testen kann. Es gibt keine sauberen Schnittstellen wo man deutlich erkennen kann was in einer Funktion als Werte hinein kommt, und was als Ergebnis von der Funktion geliefert wird. Das ist gerade für Anfänger wichtig um den Überblick behalten zu können und nicht immer über das *gesamte* Programm nachdenken zu müssen und welche Namen da global existieren und was die bedeuten und wie die Daten organisiert sind.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Keine Datenstrukturen die vom Programm verändert werden und keine Namen die von irgendwelchem Code im Modul während der Laufzeit neu zugewiesen werden. Das bedeutet kein ``global`` verwenden.

Die Ausgabe würde ich als CSV-Datei schreiben. Das kann man mit dem `csv`-Modul schreiben und lesen, und auch eine Menge anderer Software kann mit dem Format etwas anfangen.

Das `ftplib`-Modul ist ein wenig Anfängerunfreundlich, weil die `callback`-Funktion ein bisschen zu ”Verrenkungen” zwingt. Am einfachsten ist es wahrscheinlich vor dem `retrlines()`-Aufruf eine leere Liste zu erstellen und als `callback` die `append()`-Methode dieser Liste zu übergeben. Dann hat man hinterher die Zeilen in der Liste. Und daraus löscht man dann keine Einträge sondern greift direkt auf den einen Eintrag zu den man haben möchte. Schon hat man keine Liste mehr im Ergebnis.
BlackJack

Mal ein Ansatz mit echten Funktionen:

Code: Alles auswählen

from __future__ import absolute_import, division, print_function
import csv
from ConfigParser import ConfigParser
from ftplib import FTP

CONFIG_FILENAME = 'test.ini'
CSV_FILENAME = 'test.csv'
FTP_PATH = '/data/observations/metar/stations/'


def load_stations(filename):
    configuration = ConfigParser()
    configuration.read(filename)
    return dict((k.upper(), v) for k, v in configuration.items('Stations'))


def download_stations_data(station_ids):
    result = dict()
    ftp = FTP('tgftp.nws.noaa.gov', timeout=90)
    try:
        ftp.login('', '')
        ftp.cwd(FTP_PATH)
        for station_id in station_ids:
            print(station_id)
            lines = list()
            ftp.retrlines('RETR {0}.TXT'.format(station_id), lines.append)
            result[station_id] = lines[-1]
    finally:
        ftp.quit()
    return result


def get_data(config_filename):
    station_id2city_name = load_stations(config_filename)
    station_id2data = download_stations_data(station_id2city_name)
    return [
        (
            station_id,
            station_id2city_name[station_id],
            station_id2data[station_id]
        )
        for station_id in station_id2data
    ]


def append_to_file(csv_filename, rows):
    with open(csv_filename, 'a') as csv_file:
        writer = csv.writer(csv_file, delimiter=';')
        writer.writerows(rows)


def main():
    append_to_file(CSV_FILENAME, get_data(CONFIG_FILENAME))


if __name__ == '__main__':
    main()
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

@BlackJack:
Guten Morgen und vielen Dank für den neuen Ansatz.
Werde versuchen mal durchzusteigen und werde das mal heute Abend ausprobieren.
Danke für deine mühe ;-)
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

@BlackJack:
Funktioniert einwandfrei. Bin noch am "übersetzen". Verstehe noch nicht alles werde aber am Ball bleiben.

Das habe ich nun zum lesen der csv:

Code: Alles auswählen

import csv, sys

filename = 'test.csv'
with open(filename, 'rb') as f:
    reader = csv.reader(f)
    try:
        for row in reader:
            print row
            

    except csv.Error as e:
        sys.exit('file %s, line %d: %s' % (filename, reader.line_num, e))
In der csv steht:
EDDK;Koeln/Bonn;EDDK 111220Z 14008KT CAVOK 13/08 Q1006 NOSIG

EDDM;Muenchen;EDDM 111220Z 08007KT 9000 BKN006 09/08 Q1008 BECMG SCT007

EDDL;Duesseldorf;EDDL 111220Z 13006KT 9999 -RA FEW025 BKN070 11/09 Q1005 RERA NOSIG


Wie bekomme ich das jetzt in ein array damit ich array[0][0] = EDDK bekomme?
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

TheMerchant hat geschrieben:Wie bekomme ich das jetzt in ein array damit ich array[0][0] = EDDK bekomme?
Du hast doch fast alles. Um die Daten in eine Liste zu bekommen (ein array ist etwas anderes) musst du sie nur dort anfügen statt sie auszugeben.

Die simpelste Methode ist es, vor der Schleife die Liste zu erstellen (data = []) und statt des print die geparste Zeile zu der Liste hinzuzufügen (data.append(row)). Du musst natürlich noch beim Öffnen der Datei den passenden Delimiter (also das Semikolon) als Parameter mitgeben.
BlackJack

Oder man wendet einfach die `list()`-Funktion auf das `reader`-Objekt an.

Edit: Stellt sich allerdings die Frage warum man mit Indexzugriffen auf die gesamte ”Tabelle” zugreifen möchte.
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

@BlackJack
@/me

Erst einmal Danke für eure Hilfe und gedult aber....

... ich schnall das nicht.
Eine Liste ... ein Array... wie auch immer. Hier mal die lange version meines Vorhabens.
Ich habe einen Raspi. An dem habe ich ein 20x4 LCD angeschlossen und auch zum laufen genracht. Was ich nun vor habe ist:

Dateien:
station.ini = dies ist die Steuerdatei zum abrufen von ftp inhalten aus dem Netz und wird zur Laufzeit geändert damit ein Cronjob immer die aktuellen Wetterdaten aus dem Netz zieht.

Code: Alles auswählen

[Stations]
EDDK=Koeln/Bonn
EDDL=Duesseldorf
EDDM=Muenchen
update.py = diese Datei verarbeitet die staion.ini und erzeugt die metar.csv in folgendem Format:
EDDK;Koeln/Bonn;EDDK 111820Z 13006KT CAVOK 09/07 Q1004 NOSIG
EDDM;Muenchen;EDDM 111820Z 06002KT 8000 MIFG NSC 06/05 Q1005 NOSIG
EDDL;Duesseldorf;EDDL 111820Z 17008KT CAVOK 11/09 Q1003 NOSIG

Beschreibung: der Datei:
dies sind 3 unterschiedliche Flughäfen
EDDK = ICAO Code
Koeln/Bonn = Benennung des Flughafens
EDDK 111820Z 13006KT CAVOK 09/07 Q1004 NOSIG = Aktuelles Wetter am Platz.

LCD.py = Datei zum Anzeigen auf 20x4 LCD

Bild

Diese Datei soll die komplette csv einlesen und am besten in einer Liste oder Array "ablegen" damit ich miteiner Schleife und Indexen darauf zu greifen kann.
Wenn die schleife einmal Durchgelaufen ist wird die metar.csv wieder geladen und es geht wieder von neuem los.


Könnt Ihr mir sagen wie ich den Inhalt von der CSV jetzt am besten verarbeiten soll.

Danke für eure Hilfe
BlackJack

@TheMerchant: Warum mit Indexen? In 99% der Fälle ist das unnötig weil man einfach die einzelnen Elemente einer Liste nacheinander in einer Schleife abarbeitet. Und dann ist nicht einmal eine Liste nötig weil man direkt über das `reader`-Objekt iterieren kann.
TheMerchant
User
Beiträge: 18
Registriert: Montag 10. November 2014, 10:59

@BlackJack:
O.k. Wenn ich das richtig verstehe muss ich die csv in eine Liste einlesen.
Ist es eine gute Idee eine Liste pro Zeile zu machen oder alles in eine Liste.
Kannst du mir ein Beispiel zeigen
Antworten