\n ist nicht gleich \n

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
haglbrecht
User
Beiträge: 3
Registriert: Freitag 8. November 2013, 13:24

Hallo Zusammen,

ich bastle gerade an einem hoffentlich recht flexiblen programm, dass später Dateien mit div. Aufbau einlesen können soll.
Der Aufbau bzw. Inhalt der Datei wird dabei in einer Datenbank gespeichert.

Mein Problem ist nun, dass z. B. auch das Zeilenende Zeichen frei definierbar ist.
Im Regelfall ist es zwar \n, aber in manchen Formaten wird das leider auch alles in eine Zeile geschrieben und dann z. B. +++ als "Zeilenende" definiert.

Soweit ist ja alles an und für sich noch kein Problem, dachte ich.
Ich lese die Datei als ganzes ein und splitte dann die Zeile anhand des aus der Datenbank geholten Zeichens auf.
Problem ist nun, dass \n, wenn ich es aus der DB hole, nicht erkannt wird und daher die Datei nicht aufgeteilt in Zeilen wird. Mit anderen Zeichen klappt das wunderbar.
Und auch wenn ich "\n" von Hand schreibe, anstelle es aus der DB zu lesen, klappt die Funktion.

Ich komm einfach nicht drauf was ich hier falsch mache, bzw. wieso es aus der DB nicht erkannt wird.

Schon mal vielen Dank für jede anregende Idee oder Kritik.

Code: Alles auswählen

class DFU(object):
    def __init__(self, datei, dfuart):
        self.__datei = datei
        self.__art = dfuart
        rs = dblookup.DaBa().sqlselect("SELECT * FROM DFUArten WHERE ID = " + str(self.__art))
        self.__sep = rs[0].SatzEnde
        self.__filebegin = rs[0].DateiAnfang
        self.__fileend = rs[0].DateiEnde
        self.__spacer = rs[0].Spacer
        del rs

    def dateilesen(self):
        try:
            datei = open(self.__datei)
            inhalt = datei.read()
            print(self.__sep) # <-- gibt \n in der Console aus
            zeilen = inhalt.split(self.__sep)  # <-- funktioniert, wenn ich stattdessen inhalt.split("\n") schreibe

            if zeilen[0][0:len(self.__filebegin)] == self.__filebegin:
                print("Anfang OK -->" + zeilen[0][0:len(self.__filebegin)])
            else:
                print("Anfang Fehler -->" + zeilen[0][0:len(self.__fileend)])

            if zeilen[len(zeilen)-1][0:len(self.__fileend)] == self.__fileend:
                print("Ende OK" + zeilen[len(zeilen)][0:len(self.__fileend)])
            else:
                print("Ende Fehler -->" + zeilen[len(zeilen)-1][0:len(self.__fileend)])

            print(zeilen)

        except FileNotFoundError as e:
            print(e)

        finally:
            if "datei" in locals():
                datei.close()


if __name__ == "__main__":
    test = DFU(file, 1)
    test.dateilesen()
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Ich hoffe diese Zeilen machen dir einiges klar:

Code: Alles auswählen

In [1]: print r"\n"
\n

In [2]: print "\n"



In [3]: print repr("\n")
'\n'

In [4]: print repr(r"\n")
'\\n'

In [5]:
Wenn du dir unsicher bist was sich hinter einem String wirklich verbirgt ist ein print(string) der falsche Weg. Bei dir im Skript werden wahrscheinlich diese beiden Varianten verwendent.

Grüße,
anogayales
BlackJack

@haglbrecht: Anmerkungen zum Quelltext:

Abkürzungen die nicht allgemein bekannt sind, sollte man vermeiden. `DFU`, `dfuart`, `rs`, `DaBa` sind keine guten Namen. Selbst jemand der beim schreiben noch genau weiss was die Werte bedeuten, hat nach einem Jahr oft Probleme mit seinem eigenen Quelltext.

Die zwei führenden Unterstriche bei den Attributen sollten durch einen ersetzt werden. Das ist die Konvention für Attribute die nicht zum öffentlichen Teil der API gehören. Zwei führende Unterstriche sind dazu da um Namenskollisionen bei Mehrfachvererbung zu verhindern. Das ist hier nicht der Fall und wird auch äusserst selten verwendet.

Das ``del rs`` macht keinen Sinn. Am Ende der Funktion verlieren die lokalen Namen sowieso ihre Bindungen zu den Werten.

Wenn man bei der Abfrage schon das Ergebnis aus der Sequent geholt hätte, bräuchte man das im folgenden nicht jedes mal wieder tun.

`DaBa` sind eigenartig aus. Ein Objekt was nur erstellt wird um darauf eine Methode aufzurufen, ohne das man das Objekt selbst direkt braucht ist oft ein Hinweis darauf, dass man eigentlich nur eine Funktion statt einer Klasse benötigt.

Das was in der `dateilesen()` (die besser `datei_lesen()` hiesse) als `zeilen` bezeichnet wird, würde ich `datensaetze` oder `records` nennen.

`__filebegin` und `__fileend` verstehe ich auch nicht so richtig. So wie Du es geschrieben hast gehören die zu den Daten, vom Gefühl her würde ich aber sagen das ist etwas vor und nach den eigentlichen Daten und sollte entsprechend vor der Aufteilung der Datensätze geprüft und entfernt werden‽

``zeilen[len(zeilen)-1]`` hätte man auch einfach als ``zeilen[-1]`` schreiben können und ``zeilen[len(zeilen)]`` führt unweigerlich zu einem `IndexError` weil da versucht wird auf das Element *nach* dem *letzten* Element zuzugreifen.

Das ``finally`` ist schräg. Code der testet ob ein Name definiert wurde bevor er etwas damit macht kann eigentlich nicht gut sein. Man hätte weiter oben nach dem Einlesen die Datei schon schliessen können, denn dort muss der Name ja existieren sonst hätte man den Inhalt über den Namen nicht einlesen können. Ausserdem kann man sich ``finally`` und `close()` sparen wenn man die Datei mit der ``with``-Anweisung zusammen öffnet.
haglbrecht
User
Beiträge: 3
Registriert: Freitag 8. November 2013, 13:24

Vielen Dank für die schnellen Antworten.
Ich denke ich verstehe das ganze jetzt schon etwas besser.

Die Frage ist jetzt, wie kriege ich das \n aus self.__sep, so dass split es als \n, also als Zeilenumbruch, in der Datei erkennt?
Das r voranstellen klappte nicht. Auch den Wert escaped in der DB zu speichern hat nicht geklappt (\\n).

Könntet ihr mir bitte noch einen kleinen Hinweis geben, in welche Richtung ich suchen muss?



@BlackJack: Vielen Dank für diese Hinweise. Durch sowas lernt man am besten dazu :-)

Ich dachte, zwei führende Unterstriche verhindern, dass die Attribute ausserhalb der Klasse zugänglich sind. Ein Unterstrich dagegen ist nur ein Kennzeichen für die Kollegen?

die DaBa Klasse ist aktuell meine Datenbank Klasse, über welche ich meine Zugriffe darauf mache.
Darin wird aktuell im Konstruktor die Verbindung aufgebaut und im Destruktor die Verbindung geschlossen.
Deswegen habe ich auch das del rs eingebaut, um die Verbindung gleich wieder zu schließen.
Wenn man bei der Abfrage schon das Ergebnis aus der Sequent geholt hätte, bräuchte man das im folgenden nicht jedes mal wieder tun.
Klingt nach einem guten Vorschlag. Aber leider bin ich noch nicht ganz soweit, dass ich verstehe, was genau du mir damit sagen willst.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

haglbrecht hat geschrieben:Vielen Dank für die schnellen Antworten.
Ich denke ich verstehe das ganze jetzt schon etwas besser.

Die Frage ist jetzt, wie kriege ich das \n aus self.__sep, so dass split es als \n, also als Zeilenumbruch, in der Datei erkennt?
Das r voranstellen klappte nicht. Auch den Wert escaped in der DB zu speichern hat nicht geklappt (\\n).
Mach dir mal klar *warum* das Ganze nicht funktioniert. In der DB steht warscheinlich repr(self.__sep) = r"\n" = "\\n". Im Text wird aber nach "\n" gesucht. Du musst also eine Abbildung von r"\n" auf "\n" finden. Eine super schnelle Lösung wäre einfach

Code: Alles auswählen

self.__sep = self.__sep.replace(r"\n", "\n")
Mach dir aber mal klar warum das so ist.

Grüße,
anogayales
BlackJack

@haglbrecht: Um Himmels willen was meinst Du mit Destruktor? Python hat keinen deterministischen Konstruktor! Wenn Du die Verbindung wieder abbauen willst, dann musst Du das selber machen. Entweder mit einer entsprechenden Methode oder in dem Du das Objekt was die Verbindung repräsentiert zu einem Kontextmanager machst und ``with`` verwendest.

Auch zwei Unterstriche wären nur ein Hinweis, denn wie das „name mangling” funktioniert steht in der Sprachdefinition. Spätestens wenn man die gelesen hat, weiss man auch wie man an die Attribute von aussen heran kommt. Wenn man sich die Attributnamen für ein Objekt geben lässt und mal kurz nachdenkt, braucht man nicht mal die Dokumentation bemühen.

Mit dem *einmal* das Element aus der Sequenz holen meinte ich statt

Code: Alles auswählen

        rs = dblookup.DaBa().sqlselect(
            'SELECT * FROM DFUArten WHERE ID = ' + str(self.__art)
        )
        self._sep = rs[0].SatzEnde
        self._file_begin = rs[0].DateiAnfang
        self._file_end = rs[0].DateiEnde
        self._spacer = rs[0].Spacer

        # geht auch:

        row = dblookup.DaBa().sqlselect(
            'SELECT * FROM DFUArten WHERE ID = ' + str(self._art)
        )[0]
        self._sep = row.SatzEnde
        self._file_begin = row.DateiAnfang
        self._file_end = row.DateiEnde
        self._spacer = row.Spacer
Wobei mir hier gerade auffällt, dass Du einen Wert per Zeichenkettenoperationen in eine SQL-Abfrage einbaust — ganz grosses no-go. Das ist eine Sicherheitslücke. Stichwort SQL-Injection. So etwas überlässt man dem Datenbankmodul.
haglbrecht
User
Beiträge: 3
Registriert: Freitag 8. November 2013, 13:24

@anogyales:
Vielen Dank. Jetzt hab ich meinen Fehler gefunden. Problem war nicht das auslesen, sondern bereits das Schreiben in die Tabelle.
Dank deiner Tips hab ich den Wert jetzt richtig in der Tabelle gespeichert und somit hat es auch beim Auslesen geklappt. Vielen Dank :D

@BlackJack:
Vielen Dank dir für die ganze konstruktive Kritik. Aufgrund derer hab ich beschlossen, meine Lektüre zu wechseln, da darin offensichtlich zu viel Mist steht.
Hab das ganze jetzt nach deinen Empfehlungen umgearbeitet.

Code: Alles auswählen

class DFUE(object):
    def __init__(self, datei, dfue_art):
        self._datei = datei
        self._art = dfue_art
        datensatz = dblookup.DataBase().sql_select("SELECT * FROM DFUArten WHERE ID = ?", (str(self._art)))[0]
        self._sep = datensatz.SatzEnde
        self._filebegin = datensatz.DateiAnfang
        self._fileend = datensatz.DateiEnde
        self._spacer = datensatz.Spacer
        del datensatz

    def datei_lesen(self):
        with open(self._datei) as datei:
            inhalt = datei.read()
        inhalt = inhalt.strip()

        if inhalt[:len(self._filebegin)] == self._filebegin:
            print("Anfang OK --> " + inhalt[:len(self._filebegin)])
        else:
            print("Anfang Fehler --> " + inhalt[:len(self._fileend)])

        if inhalt[-len(self._fileend):] == self._fileend:
            print("Ende OK --> " + inhalt[-len(self._fileend):])
        else:
            print("Ende Fehler --> " + inhalt[-len(self._fileend):])

        datensaetze = inhalt.split(self._sep)

        for zeile in datensaetze:
            print(zeile)
Und hier auch die Klassen für den Datenbankzugriff

Code: Alles auswählen

class DataBase(object):
    def __init__(self):
        self._constr = "DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=C:\\Projekte\\Datenbank\\db.accdb;"
        self._con = pyodbc.connect(self._constr)
        self._cursor = self._con.cursor()

    #Schnelle Variante, einen einzelnen Wert aus der DB zu Fischen.
    def dlookup(self, tabelle, spalte, bedingung=None):
        query = "SELECT " + spalte + " FROM " + tabelle
        if bedingung is not None:
            query += " WHERE " + bedingung
        self._cursor.execute(query)
        row = self._cursor.fetchone()
        return row[0]

    #Select mit Parametern
    def sql_select(self, sql, parameter):
        self._cursor.execute(sql, parameter)
        row = self._cursor.fetchall()
        return row

    def sql_execute(self, sql):
        query = sql
        self._cursor.execute(query)
        self._cursor.commit()
        return self._cursor.rowcount

    def __del__(self):
        self._cursor.close()
        del self._cursor
        self._con.close
        del self._con
Ist natürlich noch alles am Anfang und in der Entwicklung, aber besonders da ist es gut, schon mal auf den richtigen Weg gebracht zu werden.
BlackJack

@haglbrecht: Die ``del``-Anweisung ist dort überall überflüssig. Da werden Namen und Attribute gelöscht die sowieso von der Laufzeitumgebung danach gelöscht werden. Wenn eine Funktion zum Aufrufer zurückkehrt werden automatisch alle lokalen Namen gelöscht, und *wenn* eine `__del__()`-Methode aufgerufen wird, dann ja kurz bevor das Objekt zerstört wird, wobei natürlich auch die Attribute gelöscht werden.

``del`` ist *kein* geeignetes Mittel um Speicher zu verwalten. Damit werden nie direkt Objekte gelöscht/freigegeben, sondern immer nur Namen oder andere Bindungen zu Werten entfernt. Ob und wann das Objekt von der Bindung freigegeben wird hängt davon ab ob noch eine andere Bindung wo anders im Programm zu dem Objekt besteht, und von noch ein paar anderen Faktoren. Zum Beispiel auch vom vorhandensein einer `__del__()`-Methode. Bei der ist erstens nicht garantiert wann sie aufgerufen wird, oder ob sie *überhaupt* aufgerufen wird. Und alleine ihr vorhandensein kann unter bestimmten Umständen dazu führen dass das Objekt niemals während des Programmlaufs freigegen wird, und demzufolge natürlich die Methode auch nicht aufgerufen wird. Konsequenz: In reinen Python-Programmen sollte man diese Methode so gut wie nie implementieren.

Hier würde man besser dem Datenbankobjekt eine `close()`-Methode spendieren und dann ``with`` mit `contextlib.closing()` verwenden um sicherzustellen das die Verbindung auf jeden Fall beim verlassen des ``with``-Blocks geschlossen wird.

Bevor man das Rad neu erfindet könnte man für die Datenbank übrigens auch SQLAlchemy in Betracht ziehen.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Statt contextlib.closing zu verwenden, könnte man der DataBase-Klasse dann auch eine __enter__- und __exit__-Methode spendieren.
Antworten