Listenzeile Splitten

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
Kellerfish
User
Beiträge: 2
Registriert: Freitag 10. April 2015, 11:33

Hallo Zusammen,

seit kurzem bin ich stolzer Besitzer eines Raspberry Pi Modell B. Damit der Kleine nicht nur sinnlos in der Ecke rumsteht, möchte ich ihn nutzen, um u. a. Python zu lernen. Dabei stehe ich aber noch ziemlich am Anfang. Ich möchte zur Zeit eine kleine "Zeiterfassung basteln" und stehe vor einem kleinen Problem.

mit

Code: Alles auswählen

import time

dateiname = "/media/medion/pi_mount/Zeiterfassung.txt"

fobj = open(dateiname, "a+")
status = ",Kommen"
wert=(time.strftime('\n'+"%d.%m.%Y,%H:%M:%S"))
fobj.write(wert)
fobj.close
schreibe ich (via Tasker und NFC-Tag per Handy) einen Zeitstempel in die genannte txt-Datei. Den "status" möchte ich später anhand einer Prüfung (if then...) entsprechend setzen, zur "Übung" schreibe ich ihn jetzt einfach manuell. Hier will ich später selber ein wenig rumbasteln.

Nun zu meiner Frage:
Um den Status abhängig von der letzten Zeile setzen zu können, muss ich ihn auslesen. Die letzte Zeile der txt-Datei kann ich bereits ermitteln und ausgeben:

Code: Alles auswählen

filename="/media/medion/pi_mount/Zeiterfassung.txt"

fobj = open(filename)
zeile = fobj.readlines()
print (zeile[-1])
Allerdings gibt er mir dann die gesamte Zeile aus, also zum Beispiel:
10.04.2015,12:39:38,Kommen

Wie kann ich nun aber nun ausschließlich den letzten Teil, also das "Kommen" extrahieren und in eine Variable übergeben, mit der ich dann weiter arbeiten kann? Versucht habe ich bisher u. a.

Code: Alles auswählen

fobj = open(filename)
mu = fobj.readlines()
x = mu.split(',')
print(mu)
das bringt mir aber den Fehler "AttributeError: 'list' object has no attribute 'split'.

Ich habe schon viel gegoogelt und ausprobiert, aber noch nichts gefunden. :(

Ein Hinweis auf die richtige Quelle wäre schon hilfreich, aber mein Englisch ist nicht das beste :(

Vielen Dank für eure Unterstützung.

Grüße,
Stefan

P.S. Ich denke, es gibt für mein Vorhaben bereits fertige Lösungen, aber ich bastele halt gerne und dabei lerne ich einfach mehr, als wenn ich was fertiges hernehm :)
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Im ersten Code hast du zeile[-1] verwendet um den letzten String aus der Liste zu bekommen. Bei deinem zweiten Code brauchst du nur das gleiche zu tun. split operiert auf einem String, nicht auf einer Liste.

Code: Alles auswählen

x = mu[-1].split(',')
Dann solltest du auch noch zusehen, dass du sinnvolle Namen für deine Bezeichner verwendest. Die anderen Unschönheiten im Programm wird bestimmt noch jemand anders kommentieren. :mrgreen:
Kellerfish
User
Beiträge: 2
Registriert: Freitag 10. April 2015, 11:33

Danke für die schnelle Antwort :)

ich steh ja noch ganz am Anfang, da wär ich froh wenn mich noch jemand "rügt" und auf die Fehler hinweist :)
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Da die Elemente in den Zeilen alle durch Kommas getrennt zu sein scheinen, könntest du noch einen Blick auf das csv-Modul werfen. Das nimmt dir dann die ganze Arbeit ab und kümmert sich auch um nervige Sonderfälle.
Das Leben ist wie ein Tennisball.
BlackJack

@Kellerfish: Weitere Anmerkungen: Unter Unix ist es üblich '\n' als Zeilenende zu verwenden und nicht als Zeilentrenner, also das Zeichen tatsächlich ans Ende *jeder* Zeile zu schreiben, auch und gerade ans Ende der letzten Zeile einer Textdatei. Denn sonst funktionieren bestimmte Sachen die man recht häufig mit Textdateien macht nicht reibungslos, wie das verbinden von Textdateien in dem man sie einfach nacheinander verarbeitet. Wenn die letzte Zeile einer Datei dann nicht durch ein '\n' abgeschlossen ist, dann ist sie nicht von der ersten Zeile der nächsten Datei abgetrennt.

Konstanten werden per Konvention komplett gross geschrieben.

Auf Modulebene sollte man nur Code stehen haben der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst und es wird durch folgendes Idiom aufgerufen:

Code: Alles auswählen

if __name__ == '__main__':
    main()
Dadurch kann man das Modul als Programm aufrufen, aber auch importieren und zwar ohne das die `main()`-Funktion automatisch ausgeführt wird. Dann kann man zum Beispiel Sachen aus dem Modul interaktiv oder automatisiert testen, oder in anderen Modulen wiederverwenden. Und einige Werkzeuge, zum Beispiel zum erstellen von Dokumentation, erwarten auch das man Module importieren kann ohne das dabei irgend etwas ”passiert”.

`fobj` ist kein besonders guter Name. Es ist eine Verquickung von einer Abkürzung mit einem nichtssagenden `obj` dahinter. Da in Python *jeder* Wert ein Objekt ist, könnte man `obj` an *jeden* Namen anhängen, was aber Null Informationsgehalt für den Leser hat. Das Objekt ist eine Datei und die hat eine Bedeutung, und diese Bedeutung sollte man in dem Namen wiederfinden. Ähnliches gilt für den Namen `wert`.

Die Klammern um den Funktionsaufruf bei der Definition von `wert` sind überflüssig. Genau wie die Operation die beiden literalen Zeichenketten zu einer zu verbinden. Das hätte man gleich als *eine* Zeichenkette schreiben können. Und wie weiter oben schon erwähnt gehört das Zeilenende-Zeichen ans Ende und nicht an den Anfang.

`status` wird in dem Quelltext definiert aber dann überhaupt nicht verwendet‽

Bei Zeitangaben würde ich das `datetime`-Modul den eher low-level, C-ähnlichen Funktionen aus dem `time`-Modul vorziehen.

Der Zeitstempel ist *ein* Wert und wenn das Komma als Spaltentrenner in der Datei verwendet wird, dann trennt man damit den einen Wert unnötig in zwei Teilwerte auf. Eventuell möchte man hier auch eine standardisiertere Form der Zeitangabe zum Beispiel nach ISO 8601 verwenden und eventuell auch die Differenz zu UTC mit speichern. Das kann auch innerhalb ein und der selben Zeitzone sinnvoll sein um mit der Umstellung Sommerzeit/Winterzeit korrekt umgehen zu können wenn man anhand der An- und Abmeldezeiten irgendwann einmal Zeitspannen ausrechnen möchte.

Die `close()`-Methode muss man auch *aufrufen* und nicht nur referenzieren. Besser wäre es die Datei zusammen mit der ``with``-Anweisung zu öffnen, dann passiert das schliessen auf jeden Fall wenn der Programmfluss den ``with``-Block verlässt — egal aus welchem Grund.

`mu` und `x` wären dann wieder schlecht gewählte Namen in diesem Zusammenhang.

Statt mit `readlines()` alle Zeilen komplett in eine Liste in den Arbeitsspeicher einzulesen obwohl man nur an der letzten Zeile interessiert ist, könnte man auch in einer ``for``-Schleife über das Dateiobjekt iterieren (bzw. das entsprechende Objekt das mit dem `csv`-Modul erstellt wurde). Nach der Schleife hat man dann in der Laufvariablen die letzte Zeile (bzw. den letzten Datensatz). Natürlich nur unter der Voraussetzung das die Datei nicht leer ist — den Fall muss man auch berücksichtigen.

So klein das Programm bisher auch aussehen mag: Man ist IMHO schon an einem Punkt wo man anfangen sollte die Umsetzung auf Funktionen aufzuteilen die jeweils ein in sich abgeschlossenes Teilproblem lösen. Das macht das Programm übersichtlicher und man kann einzelne Teillösungen besser und isoliert vom Rest testen.
BlackJack

Mal ein Beispiel wie das aussehen könnte:

Code: Alles auswählen

#!/usr/bin/env python
import csv
import errno
from datetime import datetime as DateTime

LOG_RECORDS_FILENAME = 'test.csv'
TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
LOGIN = 'login'
LOGOUT = 'logout'


class EmptyLog(Exception):
    pass


def load_last_entry(filename):
    try:
        with open(filename, 'rb') as log_records_file:
            row = None
            for row in csv.reader(log_records_file):
                pass
            if row is None:
                raise EmptyLog()
            return [DateTime.strptime(row[0], TIMESTAMP_FORMAT), row[1]]
    except IOError as error:
        if error.errno == errno.ENOENT:
            error = EmptyLog()
        raise error


def add_entry(filename, state):
    with open(filename, 'ab') as log_records_file:
        csv.writer(log_records_file).writerow(
            [format(DateTime.utcnow(), TIMESTAMP_FORMAT), state]
        )


def add_toggled_entry(filename):
    try:
        _, previous_state = load_last_entry(filename)
    except EmptyLog:
        state = LOGIN
    else:
        if previous_state == LOGIN:
            state = LOGOUT
        elif previous_state == LOGOUT:
            state = LOGIN
        else:
            raise ValueError('unknown state {0!r}'.format(previous_state))
    add_entry(filename, state)


def main():
    add_toggled_entry(LOG_RECORDS_FILENAME)


if __name__ == '__main__':
    main()
Die Zeiten werden in UTC-Zeit in einer standardisierten Form gespeichert. UTC empfiehlt sich eigentlich immer wenn man Zeitangaben mit dem Rechner verarbeiten möchte. Wenn man Zeiten dem Benutzer anzeigen möchte gibt es das externe `pytz`-Paket mit dem man Zeiten in ”timezone aware”-Objekte in seiner Zeitzone umwandeln kann.

Das Programm behandelt auch die beiden Fälle das a) keine Datei vorhanden ist, und b) das eine Datei vorhanden aber leer ist.
Antworten