Seite 1 von 2

FileDB

Verfasst: Montag 27. Juni 2011, 09:59
von lynadge
Hallo Leute,

ich wollte heute meine kleine Lib, FileDB vorstellen. Ist nix großes, ich habe sie für meinen kleinen Blog erstellt und um überhaupt einmal ein kleines Projekt auf Github liegen zu haben. Vom Prinzip her wird auch nur via "pickle" ein dictionary in eine Binärdatei abgelegt und wieder geholt. dazu habe ich noch ein paar Methoden zum besseren handhaben erstellt.

Vielleicht gefällt es euch ja und der ein oder andere kann was mit anfangen. Vorrangig stell ich aber das Projekt hier vor um mir Tipps zu hohlen ob ich alles halbwegs korrekt gemacht habe und was man noch verbessern könnte.

Viel Spaß damit!

https://github.com/deadshox/FileDB

Code: Alles auswählen

# -*- coding: utf-8 -*-
"""
FileDB
------

FileDB is a small handler to use files as database.

Copyright (c) 2011 Daniel Brüggemann.
License: WTFPL2, see LICENSE.txt for details.
"""

__author__ = 'Daniel Brüggemann'
__version__ = '0.5'
__license__ = 'WTFPL2'

import os
import pickle
import zipfile
import time
import logging
import operator
import errno
import hashlib

class FileDB:
    # -------------------------------------------------------------------------
    def __init__(self, db, filebase='filebase', logname='filedb.log'):
        self._filebase = filebase
        self._path = os.path.join('.', filebase + os.sep)
        self._bin = os.path.join(self._path, '{0}.bin'.format(db))
        self.data = {}
        self.checksum = ''
        self.new_checksum = ''

        # set logger
        logging.basicConfig(filename=logname)

        # create 'base' if not exist
        try:
            os.mkdir(self._path)
        except OSError as e:
            if e.errno == errno.EACCES:
                logging.error('Permission denied to create Folder: "{0}".'\
                              .format(self._path))
                raise
            elif e.errno == errno.EEXIST:
                logging.warning('Folder: "{0}" already exist.'\
                                .format(self._path))
        else:
            logging.warning('Create init path: "{0}".'.format(self._path))

            self._bin = os.path.join(self._path, '{0}.bin'.format(db))

        self.load()


    # -------------------------------------------------------------------------
    def load(self):
        """Load data from file."""

        try:
            with open(self._bin, 'rb') as handle:
                self.data = pickle.load(handle)
        except IOError as e:
            if e.errno == errno.ENOENT:
                try:
                    open(self._bin, 'w').close()
                except IOError:
                    logging.error('Can\'t create new file in load(): "{0}".'\
                                  .format(self._bin))
                else:
                    self.checksum = self.get_checksum()
                    logging.warning('Create file: "{0}", given was not found.'\
                                    .format(self._bin))
            elif e.errno == errno.EACCES:
                logging.error('Permission denied to given file: "{0}".'\
                              .format(self._bin))
        except EOFError:
            logging.warning('EOFError in given file: "{0}".'.format(self._bin))
        else:
            self.checksum = self.get_checksum()

    # -------------------------------------------------------------------------
    def reload(self):
        """Reload the data if checksum has changed."""

        self.new_checksum = self.get_checksum()

        if not self.checksum == self.new_checksum:
            try:
                with open(self._bin, 'rb') as handle:
                    self.data = pickle.load(handle)
            except IOError:
                logging.error('Can\'t open the given file in reload(): "{0}".'\
                              .format(self._bin))

    # -------------------------------------------------------------------------
    def save(self):
        """Save data to file."""

        try:
            with open(self._bin, 'wb') as handle:
                pickle.dump(self.data, handle)
        except IOError:
            logging.error('Error while save file: "{0}" not found!'\
                          .format(self._bin))
            raise

    # -------------------------------------------------------------------------
    def sort_by(self, sort='id'):
        """Sort data by key."""

        l = []
        for i in self.data:
            l.append(self.data[i][sort])

        l.sort()
        l.reverse()

        return l

    # -------------------------------------------------------------------------
    def get_checksum(self):
        """Get sha1 checksum of a given file."""

        stat = os.stat(self._bin)

        h = hashlib.sha1()
        h.update('{0}{1}'.format(stat.st_size, stat.st_mtime))

        return h.hexdigest()

    # -------------------------------------------------------------------------
    def create_id(self, data):
        """Creates an id as a hex digget from a integer."""

        try:
            return hex(int(data))[2:]
        except (TypeError, ValueError):
            logging.error('Given data must be a integer: "{0}".'.format(data))

    # -------------------------------------------------------------------------
    def del_db(self):
        """Delete the db."""

        try:
            os.remove(self._bin)
        except OSError:
            logging.error('Could not remove db: "{0}".'.format(self._bin))

    # -------------------------------------------------------------------------
    def del_base(self):
        """Delete the db base."""

        try:
            os.rmdir(self._path)
        except OSError:
            logging.error('Directory: "{0}" is not empty, please clear it.'\
                          .format(self._path))

    # -------------------------------------------------------------------------
    def backup(self, backup_path=''):
        """Backup the filebase or only a file."""

        try:
            os.mkdir(backup_path)
        except OSError:
            pass
        else:
            logging.warning('Backup path: "{0}" created.'.format(backup_path))

        filename = time.strftime('%Y%m%d%H%M', time.localtime(time.time()))
        f = os.path.join(backup_path, '{0}.zip'.format(filename))

        with zipfile.ZipFile(f, 'w') as backup:
            backup.write(self._filebase)
            for bkp_file in os.listdir(self._path):
                backup.write(os.path.join(self._filebase, bkp_file),
                             compress_type=zipfile.ZIP_DEFLATED)
Gruß, deadshox

UPDATE: Version 0.2 - 18.07.2011
UPDATE: Version 0.3 - 18.07.2011
UPDATE: Version 0.4 - 26.07.2011
UPDATE: Version 0.5 - 28.07.2011

Re: FileDB

Verfasst: Montag 27. Juni 2011, 10:20
von snafu
Zum Zusammensetzen von Pfaden eignet sich `os.path.join()` besser als das, was du da mit `os.sep` betreibst.

Funktionen/Methoden sollten, sofern sie nicht zum reinen Überprüfen gedacht sind, nicht `True` oder `False` zurückgeben. Für diesen Anwendungsfall wurden Exceptions erfunden, von denen man auch ruhig Gebrauch machen sollte. Im Fehlerfall wird die Ausnahme geworfen, ansonsten wird einfach nichts zurückgegeben (außer natürlich man hat irgendwelche Werte, die man ausgeben will oder sowas). Damit erspart sich der Anwender unnötiges C-artiges Überprüfen des Rückgabewerts, was sowohl bei der Implementierung als auch bei der Anwendung für weniger Code sorgt.

Und noch was: Ein "blankes" `except` ist nicht gut, da es die Fehlersuche beim Programmieren erschwert, wenn z.B. ein `NameError` geworfen wird. Lieber die konkrete Ausnahme(n) hinschreiben, die man behandeln möchte. Was du das machst, ist jeglichen Fehler in eine allgemeine Fehlermeldung zu "übersetzen", was die eigentlich Ursache verschleiert. Es ist in Python durchaus üblich, dass die verwendeten Funktionen ggf ihre eigenen Exceptions ohne große Nachbehandlung werfen.

Re: FileDB

Verfasst: Montag 27. Juni 2011, 10:38
von lynadge
Danke. :)

Das sind noch doofe Angewohnheiten, ich werde es nachbessern.

Re: FileDB

Verfasst: Montag 27. Juni 2011, 10:50
von cofi

Code: Alles auswählen

# sort the data from file -------------------------------------------------
    def sort_by(self, sort='id'):
        list = []
        for i in self._data:
            list.append(self._data[i][sort])

        list.sort()
        list.reverse()

        return list
kann man zu (ungetestet)

Code: Alles auswählen

import operator
def sort_by(self, sort='id'):
    return sorted(self._data.values(), key=operator.itemgetter(sort), reverse=True)
Umschreiben und spart sich das manuelle rumkopieren. Daneben wird mir das umdrehen nicht ganz klar.

Statt deiner "Schlagzeilen"-Kommentare über den Funktionen solltest du lieber Docstrings benutzen.

Was du da in `check` treibst ist reichlich gefaehrlich, wenn denn jetzt jemand wirklich eine Datei namens "somefile.bin" löschst du sie.[1]

Bei den blanken `excepts`, die Snafu schon erwähnt hat, solltest du zumindest noch die Exceptions selbst abfangen und ihre Fehlermeldung mitloggen. Das gilt auch wenn du das in `except Exception:` änderst. Auch hier ist `except Exception as e:` besser (mit der Ausgabe natürlich).

[1] Das zieht auch ein paar rechtliche Probleme nach sich, da du nirgends bekannt gibst das du das machst, kann man evtl von Arglist sprechen und dich so haftbar machen -- was ansonsten bei "Schenkungen" nicht geht. Ich bin kein Jurist, aber hier solltest du vorsichtig sein.

Re: FileDB

Verfasst: Montag 27. Juni 2011, 11:10
von deets
Wozu ist das Modul denn eigentlich gut? Also, warum nicht mit Python Bordmitteln wie pickle, shelve, sqlite und berkley-db arbeiten?

Re: FileDB

Verfasst: Dienstag 28. Juni 2011, 21:16
von microkernel
Hallo,

um Pfade mit einander zu verbinden, wie in Zeile 31:

Code: Alles auswählen

self._path = '.' + os.sep + self._filebase + os.sep
Würde ich lieber:

Code: Alles auswählen

self._path = os.path.join('.', self._filebase)
verwenden.

Lg
microkernel

Re: FileDB

Verfasst: Dienstag 28. Juni 2011, 21:18
von microkernel
Ach verdammt! Ich sollte lieber erstmal die Antworten (z.B.: von @snafu) lesen... :roll:

Re: FileDB

Verfasst: Mittwoch 29. Juni 2011, 08:08
von lynadge
Welchen Vorteil bringt 'os.path.join' gegenüber 'os.sep'?

@snafu, wurde alles in die TODO übernommen. :)

@cofi, die Sortier-Methode habe ich deshalb so geschrieben da ich aus irgendwelchen gründen nicht mit 'sorted' zufrieden war. Ich werde es noch mal mit in die TODO auf nehmen und testen. ;)

Das mit der Check-Methode, da hast du recht, das werde ich überarbeiten. Habe ich gar nicht bedacht das jemand seine Datei 'somefile.bin' nennen könnte, dachte der Name ist schon recht unnormal. Aber ich sehe noch etwas was ich ändern sollte. Das einzig gute, die Methode wird erst bei bedarf genutzt, nicht Automatisch. :P Aber danke für den Hinweis.

@deets, das Modul ist eine Art Wrapper mit Zusatzfunktionen für Pickle. Z.b. hat Pickle keine Backupfunktion. Der Grund warum ich keine vorgefertigte Lösung oder Datenbank verwende ist der, das ich an dem Projekt lernen möchte.

Gruß, deadshox

Re: FileDB

Verfasst: Mittwoch 29. Juni 2011, 08:55
von snafu
deadshox hat geschrieben:Welchen Vorteil bringt 'os.path.join' gegenüber 'os.sep'?
Das Zusammensetzen von Pfaden ist damit IMHO schöner (wie bereits erwähnt). In deinem Fall würde ich es aber ganz weglassen, denn dein erzeugtes `./filebase/` ist hier so ziemlich das selbe wie `filebase` - eine relative Pfadangabe eben. Das spätere `glob('./filebase/*')` entspricht einem `os.listdir('filebase')`. Soll heißen: Du machst die Dinge hier eigentlich komplizierter als nötig. ;)

Re: FileDB

Verfasst: Montag 18. Juli 2011, 12:01
von lynadge
Hab jetzt mal das Update gemacht, was auch eure Verbesserungsvorschläge mit einbezieht. ;)

Re: FileDB

Verfasst: Montag 18. Juli 2011, 12:45
von EyDu
Hallo.

Einiges wurde glaube ich schon genannt:

- Die """...""" sind keine Kommentare. Kommentage werden mit einem `#` eingeleitet
- Die ganzen "-------------------------------" sind vollkommen unnötig
- Suche mal nach Docstrings
- `file` is ein built-in, du solltest daher daran keine eigenen Objekte binden
- Dateien solltest du mit dem with-Statement öffnen
- Benutze keine leeren except-Anweisungen, gib immer an, welche Fehler bearbeitet werden
- Setze Strings nicht mit `+` zusammen, sondern benutze String Formatting
- Du brauchst nicht zu zu prüfen, ob es sich um eine Datei handelt, versuche sie einfach zu öffnen und warte ggf. die Fehlermeldung ab
- Wenn du get- und set-Methoden hast, welche nicht mehr machen als einen übergebenen Wert zu setzten, oder den gesetzten als Ergebnis zu liefern, dann kannst du dir die Methoden auch sparen.
- `list` ist wieder ein built-in-Name und sollte nicht überschrieben werden
- `file` ist auch ein schlechter Name, wenn es sich bei dem Inhalt und einen Pfad handelt

Re: FileDB

Verfasst: Montag 18. Juli 2011, 19:38
von lynadge
EyDu hat geschrieben: - Die """...""" sind keine Kommentare. Kommentage werden mit einem `#` eingeleitet
- Suche mal nach Docstrings
- `file` is ein built-in, du solltest daher daran keine eigenen Objekte binden
- Dateien solltest du mit dem with-Statement öffnen
- Setze Strings nicht mit `+` zusammen, sondern benutze String Formatting
- `list` ist wieder ein built-in-Name und sollte nicht überschrieben werden
- `file` ist auch ein schlechter Name, wenn es sich bei dem Inhalt und einen Pfad handelt
Danke, habs geändert. :) Hatte das mit den docstrings erst falsch verstanden.
EyDu hat geschrieben: - Die ganzen "-------------------------------" sind vollkommen unnötig
Finde sie machen den Quellcode aber ein wenig übersichtlicher. ;)
EyDu hat geschrieben: - Benutze keine leeren except-Anweisungen, gib immer an, welche Fehler bearbeitet werden
Ich logge doch bei jeder Exception einen Fehler mit oder habe ich das etwa falsch verstanden?
EyDu hat geschrieben: - Du brauchst nicht zu zu prüfen, ob es sich um eine Datei handelt, versuche sie einfach zu öffnen und warte ggf. die Fehlermeldung ab
Wo meinst du das genau?
EyDu hat geschrieben: - Wenn du get- und set-Methoden hast, welche nicht mehr machen als einen übergebenen Wert zu setzten, oder den gesetzten als Ergebnis zu liefern, dann kannst du dir die Methoden auch sparen.
Könnte aber vielleicht für die Zukunft nützlich sein. Denke ich.

Re: FileDB

Verfasst: Montag 18. Juli 2011, 19:58
von deets
deadshox hat geschrieben: Könnte aber vielleicht für die Zukunft nützlich sein. Denke ich.
Wenn es das werden sollte, dann kannst du immer noch ein property draus machen. Python ist nicht Java. Getter & Setter sind unnoetiger Boilerplate-Code. Und programmieren auf eine schoene Zukunft hin klappt selten bis nie.

Re: FileDB

Verfasst: Dienstag 19. Juli 2011, 09:35
von EyDu
Hallo.
deadshox hat geschrieben:
EyDu hat geschrieben: - Die ganzen "-------------------------------" sind vollkommen unnötig
Finde sie machen den Quellcode aber ein wenig übersichtlicher. ;)
Dann benutze doch einfach ein oder zwei Leerzeilen mehr ;-)

deadshox hat geschrieben:
EyDu hat geschrieben: - Benutze keine leeren except-Anweisungen, gib immer an, welche Fehler bearbeitet werden
Ich logge doch bei jeder Exception einen Fehler mit oder habe ich das etwa falsch verstanden?
Wenn du eine `except` hast, welches ohne eine Einschränkung der Fehlermeldung arbeitet, dann verschluckst du wirklich alle Fehler. Damit hast du gleich mehrere Probleme. Dadurch, dass du alle Fehler abfängst, ist nicht sofort ersichtlich wo der Fehler auftritt. Unter Umständen bekommst du das Problem erst in einer anderen Methode mit, welche das Fehlersuchen im Prinzip unmöglich macht. Zum anderen fängst du damit wirklich alle Fehler ab, auch jene, die du nicht behandeln kannst oder mit denen du nicht einmal gerechnet hast. Mal ein simples Beispiel:

Code: Alles auswählen

        try:
            with open(self._bin, 'rb') as handle
                int("spam")
                self._data = pickle.load(handle)
        except:
            logging.warning('No data obtained.')
Dann hast du zwar geloggt, dass etwas nicht funktioniet hat, du hast aber im Prinzip keine Ahnung wo es passiert ist. Fehler, welche du nicht behandeln kannst, solltest du daher auch gar nicht erst behandeln. Das macht eine Fehlersuche im Prinzip unmöglich und produziert im schlimmsten Fall einen inkonsistenten Zustand, der noch viel größere Probleme aufwirft.
deadshox hat geschrieben:
EyDu hat geschrieben:- Du brauchst nicht zu zu prüfen, ob es sich um eine Datei handelt, versuche sie einfach zu öffnen und warte ggf. die Fehlermeldung ab
Wo meinst du das genau?
In deiner `_load`-Methode. Versuche die Datei zunächst lesend zu öffnen (die "Standard Fall" sollte möglichst als erstes kommen) und wenn das nicht funktioniert versuche das Problem zu behandeln. Du solltest hier auch bedenken, dass `self._bin` zum Zeitpunkt des `isfile`-Test noch existiert, beim lesenden öffnen aber bereits nicht mehr. So etwas passiert ständig ;-)
deadshox hat geschrieben:
EyDu hat geschrieben: - Wenn du get- und set-Methoden hast, welche nicht mehr machen als einen übergebenen Wert zu setzten, oder den gesetzten als Ergebnis zu liefern, dann kannst du dir die Methoden auch sparen.
Könnte aber vielleicht für die Zukunft nützlich sein. Denke ich.
Dafür gibt es in Python `properties` um diesen Fall sauber und transparent zu handhaben. Wirf dazu mal die Suchmaschine deiner Wahl an.

Sebastian

Re: FileDB

Verfasst: Dienstag 19. Juli 2011, 12:42
von lynadge
Danke euch beiden, die getter und setter Methoden habe ich wieder entfernt und die Eigenschaft 'data' auf global gesetzt.
EyDu hat geschrieben: Dann benutze doch einfach ein oder zwei Leerzeilen mehr ;-)
Hm, das ist dann wohl Geschmackssache. Ich bevorzuge meine Methode, da sie es auch einfacher macht, die 79zeichen einzuhalten und sie lockert alles ein wenig auf. :)
EyDu hat geschrieben: Wenn du eine `except` hast, welches ohne eine Einschränkung der Fehlermeldung arbeitet, dann verschluckst du wirklich alle Fehler. Damit hast du gleich mehrere Probleme. Dadurch, dass du alle Fehler abfängst, ist nicht sofort ersichtlich wo der Fehler auftritt. Unter Umständen bekommst du das Problem erst in einer anderen Methode mit, welche das Fehlersuchen im Prinzip unmöglich macht. Zum anderen fängst du damit wirklich alle Fehler ab, auch jene, die du nicht behandeln kannst oder mit denen du nicht einmal gerechnet hast. Mal ein simples Beispiel:

Code: Alles auswählen

        try:
            with open(self._bin, 'rb') as handle
                int("spam")
                self._data = pickle.load(handle)
        except:
            logging.warning('No data obtained.')
Dann hast du zwar geloggt, dass etwas nicht funktioniet hat, du hast aber im Prinzip keine Ahnung wo es passiert ist. Fehler, welche du nicht behandeln kannst, solltest du daher auch gar nicht erst behandeln. Das macht eine Fehlersuche im Prinzip unmöglich und produziert im schlimmsten Fall einen inkonsistenten Zustand, der noch viel größere Probleme aufwirft.
/quote]
Also dann eher so etwas wie:

Code: Alles auswählen

try:
    with open(self._bin, 'rb') as handle
        int("spam")
        self._data = pickle.load(handle)
except Exeption, e:
    logging.error('Error: ' % e)
Wenn nicht, kannst du etwas genauer werden? Wenn es oberes nicht ist, kann ich mir ja das try:except gleich sparen.
EyDu hat geschrieben: In deiner `_load`-Methode. Versuche die Datei zunächst lesend zu öffnen (die "Standard Fall" sollte möglichst als erstes kommen) und wenn das nicht funktioniert versuche das Problem zu behandeln. Du solltest hier auch bedenken, dass `self._bin` zum Zeitpunkt des `isfile`-Test noch existiert, beim lesenden öffnen aber bereits nicht mehr. So etwas passiert ständig ;-)
Ach, jetzt versteh ich. :)

Re: FileDB

Verfasst: Dienstag 19. Juli 2011, 13:00
von BlackJack
@deadshox: Wenn Du hinter den Logging-Aufruf nicht noch ein ``raise`` setzt, dann kannst Du Dir das ``except`` vielleicht wirklich sparen. Die Frage ist doch an jeder Stelle, ob Du die Ausnahme überhaupt ”verschlucken” solltest. In dem Beispiel hat irgendetwas beim Laden/Parsen der Datei nicht funtkioniert. Das Programm würde aber weitermachen, als wäre nichts passiert. Sollte es das tun? Denn danach wird ja sicher mit den Daten irgendetwas gemacht. Also mit den Daten die nicht geladen werden konnten — also wird es dort entweder wieder eine Ausnahme geben, oder ein falsches Ergebnis.

Re: FileDB

Verfasst: Dienstag 19. Juli 2011, 13:04
von EyDu
deadshox hat geschrieben:Also dann eher so etwas wie:

Code: Alles auswählen

try:
    with open(self._bin, 'rb') as handle
        int("spam")
        self._data = pickle.load(handle)
except Exeption, e:
    logging.error('Error: ' % e)
Wenn nicht, kannst du etwas genauer werden? Wenn es oberes nicht ist, kann ich mir ja das try:except gleich sparen.
`Exception` fängt noch immer alle Fehler ab, du muss schon prüfen, welche Fehler genau auftreten können. Dazu hilft ein Blick in die Dokumentation zu `open`. Die mögliche Exception ist `IOError`, also behandelst du genau diese:

Code: Alles auswählen

try:
    with open(self._bin, 'rb') as handle
        int("spam")
        self._data = pickle.load(handle)
except IOError:
    logging.error("Could not open file: %s" % self._bin)
Der `ValueError` läuft dann irgendwo anders auf und du bekommst den Fehler noch mit.

Re: FileDB

Verfasst: Dienstag 19. Juli 2011, 16:48
von lynadge
@blackjack, in der _load Methode macht es mMn Sinn, da, wenn die Datei nicht existiert, sie angelegt wird. Da muss ja nichts abgebrochen werden.

@EyDu, wäre Variante 1 nicht einfacher, da man die Fehlermeldung dort einfach nur zum Loggen durch reicht? Denn eine direkte Ausgabe wäre für mich persönlich nicht nötig. Evtl könnte man noch ein 'print' setzen für die Konsole. Jeden möglichen Fehler abfangen würde ich irgendwie als zu aufwendig empfinden.

Re: FileDB

Verfasst: Dienstag 19. Juli 2011, 17:13
von EyDu
Einfacher vielleicht, aber unter umständen auch vollkommen falsch. Angenommen, du möchtest zwei Werte (in diesem etwas seltsamen Szenario) vertauschen. Leider verschreibst du dich ein wenig:

Code: Alles auswählen

>>> a, b = 42, 23
>>> try:
...     a, b = beh, ah
... except:
...     pass
... 
>>> a, b                                                                                                             
(42, 23)
Ganz offensichtlich sind `ah` und `beh` nicht vorhanden, da du alle Fehler behandelst, wird dir dies aber nicht auffallen. Das Programm läuft gemütlilch weiter, obwohl hier ein ganz offensichtlicher Fehler vorhanden ist. Vielleicht irgendwann später im Verlauf des Programms gibt es einen Folgefehler, was eine Fehlersuche unglaublich anstregend machen kann. Da ist es natürlich schön, wenn du den Fehler geloggt hast, dir wird das Problem aber unter Umständen gar nicht erst aufflallen (je nach Umfang des Logs). Du verwirfst damit also nicht nur Fehler von außen, sondern möglicherweise auch Programmierfehler.

Re: FileDB

Verfasst: Dienstag 19. Juli 2011, 18:29
von lynadge
Ich hoffe ich verstehe dich nicht falsch aber ein:

Code: Alles auswählen

>>> a, b = 42, 23
>>> try:
...     a, b = beh, ah
... except Exception, e:
...     print e
... 
name 'beh' is not defined
Sagt mir doch an, was falsch gemacht wurde.

Es ist ja im Endeffekt nichts anderes als:

Code: Alles auswählen

>>> a, b = 42, 23
>>> a, b = beh, ah
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'beh' is not defined
Nur das es mehr oder weniger abgefangen wurde.

Vielleicht hätte ich noch dazu schreiben sollen das es mehr mein Ziel ist, ein reibungslosen Programmablauf zu erreichen, als eine eine Programm zu haben was mit Fehlermeldungen um sich wirft, wenn es welche gibt.