Datei auslesen

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
mjleonir
User
Beiträge: 12
Registriert: Donnerstag 18. April 2019, 14:49

Hallo,
Ich bin relativ neu in der "Programmierszene" oder zumindest Einsteiger.
Ich lerne zurzeit ein wenig Python und wollte ein Programm schreiben, welches verschiedene Sprachen unterstützt und somit aus der dafür vorhergesehenen Datei die einzelnen Zeilen Text ausliest.
Der Code sieht bisher so aus:

Code: Alles auswählen

lang_input = input("Sprache?")
lang = 'lang/'+lang_input+'.txt'

#10 column
with open(lang, 'r') as f:
    f_contents = f.read().split('\n')[9]
    print(f_contents)
    
#7 column
with open(lang, 'r') as f:
    f_contents = f.read().split('\n')[6]
    print(f_contents)
(Bei #10 column wird die 10. Zeile ausgelesen, bei #7 column die 7.)

Meine Frage war nun, ob man diesen Code nicht "kürzer" gestalten kann, ich habe mir das so ähnlich vorgestellt:
f_contents = f.read().split('\n')[9 and 6]
print(f_contents)

Nur dass das so eben nicht klappt, da er dann nur Zeile 7 (6) ausgibt, da f_contents natürlich nur einen Wert gleichzeitig haben kann.
Hat da jemand vielleicht einen schönen Vorschlag? :)
Für einen ganz neuen Ansatz wäre ich natürlich auch offen.
Hier nochmal ein Bild wie das ganze dann aussieht wenn der Code ausgeführt wird:
Bild
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mjleonir: Was bedeutet `lang`? Und warum gibt es kein `kurz`? Spass beiseite: Gewöhne dir am besten gar nicht erst an kryptische Abkürzungen für Namen zu verwenden. Wenn `language` gemeint ist, dann schreib auch `language`.

Zudem wäre `lang_input` eigentlich `language` und `lang` wäre `language_filename`.

„Column“ bedeutet auf Deutsch „Spalte“, die Kommentare sind also etwas verwirrend wenn man eigentlich „Zeile“ meint.

Zeichenketten und Werte mit ``+`` zusammenstückeln ist eher BASIC als Python. In Python gibt es dafür Zeichenkettenformatierung mit `format()` oder ab Python 3.6 auch f-Zeichenkettenliterale.

Beim öffnen von Textdateien sollte man immer die Kodierung explizit angeben.

Du kannst die Datei *einmal* einlesen, am besten mit `list()` statt `read()` und `split()`, und dann aus der Liste mit den Zeilen die beiden gewünschten nacheinander per Index auswählen.

Edit:

Code: Alles auswählen

#!/usr/bin/env python3
import os


def main():
    language_name = input('Sprache? ')
    filename = os.path.join('lang', language_name + '.txt')

    with open(filename, 'r', encoding='utf-8') as file:
        lines = list(file)
 
    print(lines[9], end='')
    print(lines[6], end='')


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
mjleonir
User
Beiträge: 12
Registriert: Donnerstag 18. April 2019, 14:49

__blackjack__ hat geschrieben: Donnerstag 18. April 2019, 15:12 @mjleonir: Was bedeutet `lang`? Und warum gibt es kein `kurz`? Spass beiseite: Gewöhne dir am besten gar nicht erst an kryptische Abkürzungen für Namen zu verwenden. Wenn `language` gemeint ist, dann schreib auch `language`.

„Column“ bedeutet auf Deutsch „Spalte“, die Kommentare sind also etwas verwirrend wenn man eigentlich „Zeile“ meint.

Zeichenketten und Werte mit ``+`` zusammenstückeln ist eher BASIC als Python. In Python gibt es dafür Zeichenkettenformatierung mit `format()` oder ab Python 3.6 auch f-Zeichenkettenliterale.

Beim öffnen von Textdateien sollte man immer die Kodierung explizit angeben.

Du kannst die Datei *einmal* einlesen, am besten mit `list()` statt `read()` und `split()`, und dann aus der Liste mit den Zeilen die beiden gewünschten nacheinander per Index auswählen.
Danke für die ganze Kritik :D. Was ich mir bei column gedacht habe, weiß ich auch nicht, war spät ^^.
Bin ein großer fan von kryptischen Abkürzungen ;). Aber ich versuche es verständlicher zu gestalten.
Und ja, ich habe vor Jahren mich mit SmallBASIC an das Programmieren rangetastet, vielleicht kommt es daher, dass ich das alles so zusammenstückele.
Ein Freund wollte mit mir zusammen etwas programmieren weshalb ich endlich den anreiz hatte etwas neues zu lernen.

Danke auch für den Vorschlag mit `list()`, werde ich nun probieren.
mjleonir
User
Beiträge: 12
Registriert: Donnerstag 18. April 2019, 14:49

Code: Alles auswählen

import os


def main():
    language_name = input('Sprache? ')
    filename = os.path.join('lang', language_name + '.txt')
    if not os.path.exists(filename):
        print('This language is not supported')
        main()

    with open(filename, 'r') as file:
        lines = list(file)
 
    print(lines[9], end='\n')
    print(lines[6], end='\n')


if __name__ == '__main__':
    main()
Habe den Code nun etwas abgeändert, vielen Dank nochmal, ich glaube das hat mein Problem gelöst :).
Habe durch 'import os' noch eine Abfrage eingefügt, ob die Sprache überhaupt existiert.
Das utf-8 encoding habe ich entfernt, da ich dadurch nur 'en' aufrufen konnte, nicht 'de'.

MfG
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hmja. Du scheinst wirklich noch sehr in BASIC zu stecken. Funktionen sind keine Sprungmarken wie GOTO in BASIC. Durch deinen simplen Aufruf von main() im Fehlerfall hast du jetzt eine Rekursion erzeugt. Das hat zwei Effekte: erstens geht dann irgendwann der Stack aus (ok, das sind ein paar tausend Aufrufe, aber trotzdem). Vor allem aber wird nach erfolgreichem durchlaufen von main einfach wieder zurueckgekehrt an die Stelle, wo du im Fehlerfall verzweigt bist. Und dann versucht, mit einem nicht-existierenden Pfad weiter zu machen. Probier es mal aus.

Stattdessen kannst du hier zB eine while-Schleife machen, die du erst dann verlaesst (mit break), wenn du eine existierende Datei angegeben hast.
mjleonir
User
Beiträge: 12
Registriert: Donnerstag 18. April 2019, 14:49

Code: Alles auswählen

def main():
    language_name = input('Language? ')
    filename = os.path.join('lang', language_name + '.txt')
    while not os.path.exists(filename):
        print('This language is not supported.')
        language_name = input('Language? ')
        filename = os.path.join('lang', language_name + '.txt')
        if os.path.exists(filename):
            break
Das wäre jetzt mein Ansatz für einen while-loop. Ist das zu kompliziert gemacht, oder passt das so?
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Dopplung der Variablen ist natuerlich nicht so schoen. Da nimmt man einfach eine while-True-Schleife und verlaesst die dann. Und dann das ganze auch noch in eine Funktion gepackt, so das die main einfacher wird.

Code: Alles auswählen

def get_language_filename():
    while True:
           language = input("Language?")
           full_path = os.path.join(...)
           if os.path.exists(full_path):
                   return full_path
mjleonir
User
Beiträge: 12
Registriert: Donnerstag 18. April 2019, 14:49

Ja, ist mir auch aufgefallen, muss noch viel lernen wie man sowas löst :D. Deswegen bin ich ja hier.
Danke.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mjleonir: Wenn UTF-8 falsch ist, dann musst Du da die tatsächliche Kodierung verwenden. Einfach weglassen geht nicht. Denn dann würde ich zum Beispiel Probleme mit Deinen deutschsprachigen Dateien bekommen, da bei mir standardmässig UTF-8 verwendet wird wenn ich keine Kodierung angebe.

Wobei es wahrscheinlich sinnvoll wäre wenn Du Deine Textdateien genau wie Python-Quelltexte auch UTF-8 kodiert speicherst.

Statt die Existenz der Datei vorher zu prüfen, würde man in Python eher auf die Ausnahme entsprechend reagieren. Denn zwischen Prüfung und Öffnen der Datei kann sie ja gelöscht werden, und es gibt auch noch andere Gründe warum eine Datei nicht geöffnet werden kann. Zugriffsrechte beispielsweise, oder unter Windows auch beliebt das die Datei schon von einem anderen Prozess geöffnet ist.

Wenn am Ende von der `print()`-Ausgabe ein Zeilenende-Zeichen ausgegeben werden soll, dann braucht man `end` nicht angeben – das ist schon die Voreinstellung.

Code: Alles auswählen

#!/usr/bin/env python3
import os

ENCODING = 'UTF-8'


def main():
    while True:
        language_name = input('Language? ')
        filename = os.path.join('lang', language_name + '.txt')
        try:
            with open(filename, 'r', encoding=ENCODING) as file:
                lines = list(file)
        except OSError:
            print('This language is not supported!')
        except UnicodeDecodeError:
            print(
                f'The language file {filename!r} is not {ENCODING} encoded'
                f' or corrupt!'
            )
        else:
            break
 
    print(lines[9])
    print(lines[6])


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
mjleonir
User
Beiträge: 12
Registriert: Donnerstag 18. April 2019, 14:49

__blackjack__ hat geschrieben: Donnerstag 18. April 2019, 16:40 @mjleonir: Wenn UTF-8 falsch ist, dann musst Du da die tatsächliche Kodierung verwenden. Einfach weglassen geht nicht. Denn dann würde ich zum Beispiel Probleme mit Deinen deutschsprachigen Dateien bekommen, da bei mir standardmässig UTF-8 verwendet wird wenn ich keine Kodierung angebe.
Habe nun beide Dateien noch einmal als UTF-8 gespeichert, beide waren vorher ANSI. Trotzdem hat die eine geklappt die andere nicht... Nun klappen aber beide.
__blackjack__ hat geschrieben: Donnerstag 18. April 2019, 16:40 Wenn am Ende von der `print()`-Ausgabe ein Zeilenende-Zeichen ausgegeben werden soll, dann braucht man `end` nicht angeben – das ist schon die Voreinstellung.
Ist mit dem jetzigem Code von dir auch nicht mehr nötig.. Vorhin hat er dann alles in der selben Zeile ausgegeben, weswegen ich das noch dazugemacht habe.

Danke trotzdem.
Lerne durch diesen einen Thread immer mehr ^^

MfG
mjleonir
User
Beiträge: 12
Registriert: Donnerstag 18. April 2019, 14:49

Ich habe nun etwas zum Code ergänzt:

Code: Alles auswählen

import os

ENCODING = 'UTF-8'


def main():
    while True:
        language_name = input('Language? ')
        filename = os.path.join('lang', language_name + '.txt')
        try:
            with open(filename, 'r', encoding=ENCODING) as file:
                lines = list(file)
        except OSError:
            print('This language is not supported!')
        except UnicodeDecodeError:
            print(
                f'The language file {filename} is not {ENCODING} encoded'
                f' or corrupt!'
            )
        if language_name == 'ls':
            for line_number in lines:
                print(line_number)
        else:
            break
Mit dem letzten if-statement (if language_name == 'ls') sollen alle Zeilen der Datei 'ls' ausgegeben werden.
Das funktioniert soweit auch, jedoch bleibt zwischen jeder Zeile eine weitere frei, was mich etwas stört.
Wie kann man das beheben?

Bild
mjleonir
User
Beiträge: 12
Registriert: Donnerstag 18. April 2019, 14:49

Hab es nun folgendermaßen gelöst:

Code: Alles auswählen

import sys

        if language_name == 'ls':
            for line_number in lines:
                sys.stdout.write(line_number)

also sys.stdout.write() anstatt print().
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mjleonir: Das war ja der Grund warum ich ``end=''`` bei `print()` gesetzt hatte. `line_number` ist als Name falsch. Das ist ja keine Zeilennummer sondern die Zeile selbst.

Und der Code ist falsch strukturiert: Immer wenn man etwas anderes als 'ls' als Namen eingibt wird die ``while``-Schleife verlassen. Auch wenn die Datei gar nicht geladen werden konnte. Dann ist aber `lines` undefiniert. Wenn man 'ls' eingibt, dann müsste ja vorher eine Sprachdatei erfolgreich geladen worden sein, was aber zum Abbruch der Schleife geführt hätte (es sei denn die Sprachdatei hiess ``lang/ls.txt``), womit man dann aber keine Chance mehr hat 'ls' einzugeben.

Das bei ``end=''`` beim `print()` beide Zeilen (9 und 6) in einer Zeile ausgegeben wurden, kann übrigens nur passieren wenn 9 die letzte Zeile in der Datei ist und die Zeile nicht mit einem Zeilenende-Zeichen abgeschlossen war. Zumindest unter Linux/Unix würde ich das als fehlerhafte Datei ansehen, denn das kann bei so einigen Werkzeugen die Textdateien verarbeiten zu Problemen führen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
mjleonir
User
Beiträge: 12
Registriert: Donnerstag 18. April 2019, 14:49

__blackjack__ hat geschrieben: Donnerstag 18. April 2019, 19:39 @mjleonir: Das war ja der Grund warum ich ``end=''`` bei `print()` gesetzt hatte. `line_number` ist als Name falsch. Das ist ja keine Zeilennummer sondern die Zeile selbst.

Und der Code ist falsch strukturiert: Immer wenn man etwas anderes als 'ls' als Namen eingibt wird die ``while``-Schleife verlassen. Auch wenn die Datei gar nicht geladen werden konnte. Dann ist aber `lines` undefiniert. Wenn man 'ls' eingibt, dann müsste ja vorher eine Sprachdatei erfolgreich geladen worden sein, was aber zum Abbruch der Schleife geführt hätte (es sei denn die Sprachdatei hiess ``lang/ls.txt``), womit man dann aber keine Chance mehr hat 'ls' einzugeben.

Das bei ``end=''`` beim `print()` beide Zeilen (9 und 6) in einer Zeile ausgegeben wurden, kann übrigens nur passieren wenn 9 die letzte Zeile in der Datei ist und die Zeile nicht mit einem Zeilenende-Zeichen abgeschlossen war. Zumindest unter Linux/Unix würde ich das als fehlerhafte Datei ansehen, denn das kann bei so einigen Werkzeugen die Textdateien verarbeiten zu Problemen führen.
Über die Benennungen sehe ich vorerst hinweg, geht mir erstmal um den Code selbst.
ls.txt ist auch als Sprachdatei festgelegt, auch wenn das so etwas gepfuscht und falsch ist, da diese Datei am Ende ausgibt welche Sprachen es alles gibt.
Das ganze ist in den while-loop eingebaut. Wenn man nach der Sprache gefragt wird kann man auch "ls" eingeben und einem werden alle verfügbaren Sprachen angezeigt.
Dann ist man weiterhin im loop und kann nun eine Sprache eingeben oder noch einmal "ls" wenn man Lust hat.

Ja, das kann sein, dass die letzte Zeile nicht mit einem Zeilenumbruch beendet wurde aber nun haben wir ja kein ``end=''`` mehr im code, weswegen ich zu der Lösung gegriffen habe.
Ganz falsch ist es ja nicht, oder? Bis auf die Benennung von `line_number`.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mjleonir: Benennung ist wichtig! Falsche Benennung kann Leser verwirren, inklusive den Autor selber, denn man liest Quelltext öfter als man ihn schreibt. Und wenn einem kein passender Name einfällt, ist das oft auch ein Zeichen das etwas nicht stimmt. Entweder hat man das Problem/die Lösung selbst nicht ganz verstanden, oder man versucht Daten in Objekten zusammen zu fassen, die nicht zusammen gehören.

Der Code wird problematisch wenn ``ls.txt`` nicht existiert oder nicht gelesen werden kann. Ausserdem muss der Inhalt der ``ls.txt``-Datei nicht mit den tatsächlich vorhandenen Sprachen übereinstimmen. Ich würde da ja eher direkt nach der Eingabe des Benutzers prüfen ob er eine Sprache laden oder sich die Vorhandenen auflisten lassen möchte, und die dann anhand der *.txt-Dateien im ``lang/``-Verzeichnis auflisten lassen. Statt `os.path` könnte man sich in dem Zusammenhang auch mal mit dem `pathlib`-Modul auseinander setzen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten