Wie Fehler prüfen bei Klasseninitialisierung

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
dkell
User
Beiträge: 12
Registriert: Mittwoch 12. März 2008, 09:40

Hallo

Ich habe ein Programm, welches Daten über TCP senden. Hier ein Code-Ausschnitt

Code: Alles auswählen


class Sharp(object):

    def __init__(self, host,  port):

        self.host=host
        self.port=port
        try:    
            self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.s.connect((self.host, self.port))
        except socket.error, msg:
            log.warning('Fehler bei Socket-Verbindung: %s:%d.%s ' % (self.host, self.port,  msg))  
        else:
            log.debug('Verbindung aufgebaut mit SharpTV')

    def __del__(self):
        self.s.close()
        log.debug('Verbindung geschlossen')
    
    def request(self, cmd):
        try:
            self.s.send(cmd + '\r')
            sleep(0.1)
            data = self.s.recv(8192)            
        except:
            log.warning("Befehl [%s] konnte nicht gesendet werden" % (cmd))
            return False
        else:
            return data


if __name__ == "__main__":
    sharp = Sharp('192.168.1.99',  100)
    sharp.request('POWER????')
    sharp.request('POWER1   ')

im __init__ Teil wird die Verbindung aufgebaut und mittels Try verprobt. Falls die Verbindung nicht zustande kommt laufen die sharp.request() auf Fehler. Wie und auf welcher Stufe sollte dies am besten geprüft werden, damit der nachfolgende Code nicht stehen bleibt?

Vielen dank
Dani
BlackJack

@dkell: Das Problem ist ja, dass Du die Ausnahme einfach nur loggst und dann weiter machst als wäre nichts passiert. In beiden Methoden solltest Du die Ausnahmen nicht unterdrücken, sondern in der Aufrufhierarchie weiter nach oben laufen lassen, denn das `Sharp`-Objekt ist offensichtlich nicht in der Lage die angemessen zu behandeln. Eine Methode die "Fehlercodes" zurück gibt ist nicht schön. Da passiert ganz schnell das, was Du in Deinem Quelltext ja auch machst: Du ignorierst es einfach.

Die `__del__()`-Methode ist keine gute Idee. Es ist nicht garantiert wann sie aufgerufen wird, oder ob sie *überhaupt* irgendwann aufgerufen wird. Zudem kann die Existenz so einer Methode schon dazu führen das Objekte unter bestimmten Umständen nie freigegeben werden. Das ist kein deterministischer Destruktor.

Du solltest der Klasse entweder eine `close()`-Methode spendieren und die explizit aufrufen, oder `__enter__()`/`__exit__()` für die ``with``-Anweisung implementieren.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Idealerweise sogar beides. Also eine `close()`-Methode schreiben und diese in `__exit__()` aufrufen. Ich nehme ja an, dass dies von BJ ohnehin implizit so gemeint war.
dkell
User
Beiträge: 12
Registriert: Mittwoch 12. März 2008, 09:40

Ich habe jetzt den Code mal umgebaut auf __enter__ und __exit__. Irgendwas habe ich aber noch nicht rund, da s.request() in with ... aufgerufen wird auch wenn die Verbindung nicht aufgebaut werden konnte, was dann natürlich einen Fehler verursacht. Muss ich dies in def.request() abfangen?

Vielen dank
Dani

Code: Alles auswählen

class Sharp(object):

    def __init__(self, host,  port):
        self.host=host
        self.port=port

    def __enter__(self): 
        try:    
            self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.s.connect((self.host, self.port))
        except socket.error, msg:
            print "FEHLER VERBINDUNG"
            # WAS HIER?
        else:
            return self
        
    def __exit__(self, exc_type, exc_value, traceback): 
        if self.s:
            self.s.close()    
    
    def request(self, cmd):
        try:
            self.s.send(cmd + '\r')
            sleep(0.1)
            data = self.s.recv(8192)            
        except:
            log.warning("Befehl [%s] konnte nicht gesendet werden" % (cmd))
            return False
        else:
            return data


if __name__ == "__main__":
    sharp = Sharp('192.168.1.99',  100)
    with sharp as s:
        s.request('POWER????')
BlackJack

@dkell: Das mir dem `__del__()` hatte mit dem Problem nichts zu tun.

An Stelle des Kommentars ``# WAS HIER?`` und bei dem ``return False`` in der `request()` gehört ein ``raise`` hin.
dkell
User
Beiträge: 12
Registriert: Mittwoch 12. März 2008, 09:40

BlackJack hat geschrieben:@dkell: Das mir dem `__del__()` hatte mit dem Problem nichts zu tun.

An Stelle des Kommentars ``# WAS HIER?`` und bei dem ``return False`` in der `request()` gehört ein ``raise`` hin.
Und das dann im Aufruf abfangen mit

Code: Alles auswählen

if __name__ == "__main__":
    try:
        sharp = Sharp('192.168.1.99',  100)
        with sharp as s:
            s.request('POWER????')
Sorry für die vielen Fragen aber ich Blicke da noch nicht ganz durch.

Danke
Dani
BlackJack

@dkell: Wo Du das abfängst kommt doch darauf an was Du an welcher Stelle mit der Information, dass eine Ausnahme aufgetreten ist, anfangen willst. Du kannst um "alles" ein ``try/except`` setzen, oder um einzelne Aufrufe -- je nach dem wie sinnvoll Du jeweils an der Stelle auf Ausnahmen reagieren kannst.

Ich persönlich würde wohl zum Beispiel nicht in `Sharp`-Objekten loggen, weil mir das nicht nach "höherer" Anwendungslogik aussieht.

Den Code zum Verbinden würde ich übrigens weiter in der `__init__()` lassen und in der `__enter__()` nur ``return self`` schreiben. Sonst ist man ja *gezwungen* ``with`` zu verwenden. Und Du musst dann nicht Namen wie `s` erfinden sondern kannst einfach sowas schreiben:

Code: Alles auswählen

    with Sharp('192.168.1.99',  100) as sharp:
Und so ganz sauber gehst Du mit der Socketprogrammierung nicht um, denn es könnte passieren, dass der `recv()` nicht alle Daten liefert.
dkell
User
Beiträge: 12
Registriert: Mittwoch 12. März 2008, 09:40

BlackJack hat geschrieben:
Und so ganz sauber gehst Du mit der Socketprogrammierung nicht um, denn es könnte passieren, dass der `recv()` nicht alle Daten liefert.
Hi
Vielen dank schon mal für die Tipps. Was wäre denn hier besser? Schreibe noch das eine oder andere solcher Module und wäre natürlich froh, dies professionell und richtig zu haben.

Vielen dank
Dani
BlackJack

@dkell: Was man sauber machen muss hängt vom Protokoll ab. Wenn man weiss wiviele Bytes kommen, muss man solange lesen bis man die mindestens zusammen hat, und sich eventuelle zusätzliche Daten für den nächsten Lesevorgang merken.

Wenn es ein Endkennzeichen gibt, dann muss man solange lesen bis man das in den Eingabedaten enthalten ist, und sich eventuelle zusätzliche Daten auch hier wieder für den nächsten Lesevorgang merken.
Antworten