Textfile einlesen und Variablen zum rechnen verwenden

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
ulRa
User
Beiträge: 3
Registriert: Sonntag 16. Mai 2021, 12:01

Ich möchte ein Textfile mit folgendem oder ähnlichen Inhalt einlesen,
und als Variablen zum rechnen verwenden:

Textfile "Vardat.txt" :
# Floats und Integers
a = 1.271
b = 8
# texte
c = 'text'


Kein Problem mit

fh = open('Vardat.txt')
for line in fh:
exec(line)

print('a = ',a)
print('b = ',b)
print('c = ',c)
print('d = ',3+a*b)


Wie erhofft erhalte ich Variablen, mit denen ich arbeiten bzw. rechnen kann:
a = 1.271
b = 8
c = text
d = 13.168



A B E R : mit folgendem Skript geht es nicht

from tkinter import *
from tkinter import filedialog

def leave():
ws.destroy()
ws.quit()

def readFileAndConvert():
tf = filedialog.askopenfilename(title="Open Text file", filetypes=(("Text Files", "*.txt"),))
tf = open(tf, 'r')
for line in tf:
print(line)
exec(line)
tf.close()

print('a = ',a)
print('b = ',b)
print('c = ',c)
print('d = ',3+a*b)

ws = Tk()
ws.title("loading txt-file")
ws.geometry("300x200")

commandButton = Button(ws, text="Open File", command=readFileAndConvert)
commandButton.place(x=20, y=50, width=80, height=20)

leaveButton = Button(ws, text='Leave !', command=leave)
leaveButton.place(x=20, y=150, width=80, height=20)

ws.mainloop()


Hier die Antwort:

# Floats und Integers
a = 1.271
b = 8
# texte
c = 'text'

Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "/home/ich/PYTHON_PROGRAMME/LOADFILE/DialogLoadFile.py", line 26, in readFileAndConvert
print('a = ',a)
NameError: name 'a' is not defined


Gut, einlesen klappt, aber es werden keine Variablen erzeugt.
Seit Wochen suche ich im ganzen Internet herum, aber ich bekomme immer nur Antworten, wie
ich solle mit json, pandas, pickles, csv, strip usw. arbeiten, das ist mir zu viel Affentheater.

Mit Matlab ( eval) und Scilab ( exec) get es sehr einfach. Mit Python, der Sprache der Zukunft
muß man Zaubertricks ohne Ende ausprobieren und kommt doch nicht zum Ziel.
Vielleicht weiß im Forum jemand weiter ?
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ulRa: Vergiss bitte gleich mal wieder `eval` und `exec`. In den Augen von Programmierern sind *das* nämlich die Zaubertricks. Wenn Dir eine saubere Lösung statt solcher Hacks zu viel Affentheater sind, dann bleib bei Matlab und/oder Scilab.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Hallo ulRa,

dein Code funktioniert nicht, da exec() innerhalb der Funktion ausgeführt wird. Bei tkinter kannst du das aber nicht vermeiden.
Außerdem we __blackjack__ schon sagt, sollte man exec und eval nicht verwenden, da die Zeile dann mit allen gefährlichen Nebeneffekten ausgeführt wird.

Wenn ich richtig verstanden habe, was du machen willst, kannst du so etwas ähnliches machen:

Code: Alles auswählen

line = "a = 10"

name, value = line.replace(" ", "").split("=")

print(name)
print(value)

print(f"{name} = {value}")
Ausgabe:
a
10
a = 10
ulRa
User
Beiträge: 3
Registriert: Sonntag 16. Mai 2021, 12:01

Hallo rogerb,

vielen Dank für Deinen Tip.
So bekomme ich aber noch keine Variable. Ich erweitere mal Deinen Vorschlag:

line = "a = 10"
name, value = line.replace(" ", "").split("=")
print('name =',name)
print(f"{name} = {value}")
a=(float(value))
print('a =',a)

und erhalte

name = a
a = 10
a = 10.0

Jetzt habe ich eine Variable.
Das heißt also : ich kann die Variable durch einen eingelesenen string-Ausdruck nicht erzeugen,
sondern sie muß schon existieren und ich muß ihr irgendwie den rechten Teil des gesplitteten
Ausdrucks als float zuordnen. Wenn es denn ein float sein soll und kein integer oder string.
Und es zwingt mich, mir für die auskommentierten Zeilen im Datenfile noch eine Sonderbehandlung
zu erfinden. Schöne Aussichten.
Der Hintergrund ist: Die Textdatei soll etwa 80 Zeilen nach dem Muster name = value
bzw. name = string und einige Kommentarzeilen enthalten. Die Variablen sind Parameter für Designberechnungen.
Eine Muster-GUI mit tkinter, die Berechnungen und die graphische Darstellung eines etwas komplexen
Gebildes mittels matplotlib funktionieren bereits gut. Nun will ich die Parameter auch speichern und bei Bedarf
wieder aufrufen. Allerdings möchte ich die Textdatei auch mittels Texteditor sehr einfach überblicken und evtl. editieren
können.
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@rogerb: Das Ersetzen der Leerzeichen ist keine gute Idee. Da wären dann am Ende "foo bar = 42" und "foobar=42" gleich.

@ulRa: Dann nimm JSON, YAML, CSV, TOML, …

Ist ja nicht so als gäbe es für so etwas keine etablierten Formate und Module.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
ulRa
User
Beiträge: 3
Registriert: Sonntag 16. Mai 2021, 12:01

So zusammen,
jetzt hab' ich's. Der entscheidende Tip war : name, value = line.replace(" ", "").split("=").
Die Textdatei sieht jetzt so aus :

# = Floats und Integers
a = 1.271
b = 8
# = texte
c ='text'

also Gleichheitszeichen auch in den Kommentarzeilen,
damit deren Fehlen nicht zu Beanstandungen führt. (Vielleicht geht's auch ohne.
Und das Skript:

from tkinter import *
from tkinter import filedialog

def leave():
ws.destroy()
ws.quit()

def readFileAndConvert():
fh = filedialog.askopenfilename(title="Open Text file", filetypes=(("Text Files", "*.txt"),))
fh = open(fh, 'r')
for line in fh:
name, value = line.replace(" ", "").split("=")
if name=='a':
a=(float(value))
elif name=='b':
b=(float(value))
elif name=='c':
c=(str(value))
fh.close()
return(a,b,c)

def useVars():
(a,b,c) = readFileAndConvert()

print('a = ',a)
print('b = ',b)
print('c = ',c)
print('d = ',3+a*b)

ws = Tk()
ws.title("loading txt-file")
ws.geometry("300x200")

commandButton = Button(ws, text="Open File", command=useVars)
commandButton.place(x=20, y=50, width=80, height=20)

leaveButton = Button(ws, text='Leave !', command=leave)
leaveButton.place(x=20, y=150, width=80, height=20)

ws.mainloop()

macht genau, was ich will. Es geht also doch ohne JSON, YAML, CSV, TOML.
Danke nochmal für den "Beistand"
nezzcarth
User
Beiträge: 1634
Registriert: Samstag 16. April 2011, 12:47

ulRa hat geschrieben: Sonntag 16. Mai 2021, 21:32 Es geht also doch ohne JSON, YAML, CSV, TOML.
Klar, gehen tut das schon. Die Frage ist halt, wie :). Jede Variable einzeln aus der Textdatei herauszuparsen und dann manuell an einen hard-gecodeten Namen zu binden, ist nicht besonders flexibel, nicht besonders robust und skaliert sehr schlecht; das muss dir doch auffallen.

Genau so etwas versucht man beim Programmieren jedenfalls eigentlich zu vermeiden. Und dabei hätten dir zum Beispiel die genannten Formate geholfen, für die es Parser gibt, die das in ordentlich erledigen.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

*-Importe sollte man nicht benutzen, weil dadurch keine Zuordnung von Namen zu Modulen mehr möglich ist.
Globale Variablen soll man nicht benutzen, weil dann eine Funktion keine in sich geschlossen Einheit ist, was Fehlerursache und -suche sehr komplex macht.
Variablennamen und Funktionen schreibt man nach Konvention komplett klein.
Benutze keine kryptischen Abkürzungen, was soll `ws` bedeuten?
`leave` ist unnötig, da wenn das Hauptfenster geschlossen wird, automatisch auch `quit` ausgeführt wird.
`place` sollte man nicht verwenden, weil Änderungen an der GUI dadurch sehr kompliziert werden und es nur auf einem System mit einer Bildschirmauflösung "gut" aussieht, bei anderen Systemen könnte das Fenster aber unbenutzbar sein, weil Dinge übereinander liegen, oder außerhalb des Fensters.

Dateien öffnet man mit Hilfe des with-Statements, damit garantiert ist, dass die Datei auch wieder geschlossen wird.
Für Deinen Parser würde man erst prüfen, ob es ein Kommentarzeile ist und diese dann ignorieren. Normalerweise werden auch Leerzeilen ignoriert.
Statt `split` nimmt man in diesem Fall `partition`. Das funktioniert auch, wenn kein = in einer Zeile vorkommt.
Statt einzeln Variablen zu definieren, würde man eine passende Datenstruktur verwenden, z.B. ein Wörterbuch.
In Deiner jetzigen Implementierung dürfen keine Leerzeilen vorkommen, a, b, oder c dürfen nicht fehlen, wenn a oder b fehlen, bekommst Du einen NameError, bei c wird einfach None zurückgegeben. c muß als letztes definiert werden.
Das sind alles keine schönen Eigenschaften für einen Parser, daher solltest Du etwas etabliertes nehmen, wo es schon fertige Module gibt. TOML kommt Deiner erfundenen Konfigurationssprache recht nahe.

Code: Alles auswählen

import tkinter as tk
from tkinter import filedialog
    
def read_vars():
    filename = filedialog.askopenfilename(title="Open Text file", filetypes=(("Text Files", "*.txt"),))
    result = {}
    with open(filename, encoding="utf8") as lines:
        for line in lines:
            line = line.strip()
            if not line or line.startswith('#'):
                # ignore
                pass
            else:
                name, equal_sign, value = line.partition("=")
                if not equal_sign:
                    # was auch immer bei einer falschen Zeile passieren soll.
                    raise RuntimeError("error parsing line %r" % line)
                result[name] = value
    return result

def use_vars():
    result = read_vars()
    print('a = ', result['a'])
    print('b = ', result['b'])
    print('c = ', result['c'])
    print('d = ', 3 + float(result['a']) * float(result['b']))
   
def main():
    window = tk.Tk()
    window.title("loading txt-file")
    tk.Button(window, text="Open File", command=use_vars).pack()
    tk.Button(window, text='Leave !', command=window.destroy).pack()
    window.mainloop()

if __name__ == "__main__":
    main()
Antworten