Sinn einer Basis-Klasse mit leeren Methoden

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
Benutzeravatar
Dennis89
User
Beiträge: 1578
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

ich schaue mir gerade den Code von`pySerial` an und die Entwickler schreiben eine Basis-Klasse mit leeren Methoden und überschreiben die dann in den Klassen, die davon erben. Ich frage mich, wieso?
Wenn die Klasse doch gar keine Funktionalität besitzt, wieso brauche ich die dann?

Hier geht's direkt zu der Klasse

Der Thread-Support, den `pySerial` hier bietet ist wohl noch nicht ausreichend getestet, zumindest steht in der Doku This implementation is currently in an experimental state. Use at your own risk.. Da es aber schon in der Doku erwähnt wird, denke ich nicht dass die Klasse nicht fertig ist und da noch was reinkommt.

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18291
Registriert: Sonntag 21. Oktober 2012, 17:20

Da sollte man einiges ändern, zumindest die Basisklasse auch wirklich nutzen:

Code: Alles auswählen

class Protocol(object):
    """
    Protocol as used by the ReaderThread. This base class provides empty
    implementations of all methods.
    """
    def __init__(self):
        self.buffer = bytearray()
        self.transport = None

    def connection_made(self, transport):
        """Store transport"""
        self.transport = transport

    def data_received(self, data):
        """Called with snippets received from the serial port"""

    def connection_lost(self, exc):
        """Forget transport"""
        self.transport = None
        if isinstance(exc, Exception):
            raise exc

    def handle_packet(self, packet):
        """Process packets - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_packet')


class Packetizer(Protocol):
    """
    Read binary packets from serial port. Packets are expected to be terminated
    with a TERMINATOR byte (null byte by default).

    The class also keeps track of the transport.
    """

    TERMINATOR = b'\0'

    def data_received(self, data):
        """Buffer received data, find TERMINATOR, call handle_packet"""
        self.buffer.extend(data)
        while self.TERMINATOR in self.buffer:
            packet, self.buffer = self.buffer.split(self.TERMINATOR, 1)
            self.handle_packet(packet)



class FramedPacket(Protocol):
    """
    Read binary packets. Packets are expected to have a start and stop marker.

    The class also keeps track of the transport.
    """

    START = b'('
    STOP = b')'

    def __init__(self):
        super().__init__()
        self.in_packet = False

    def connection_lost(self, exc):
        """Forget transport"""
        self.in_packet = False
        del self.buffer[:]
        super().connection_lost(exc)

    def data_received(self, data):
        """Find data enclosed in START/STOP, call handle_packet"""
        for byte in serial.iterbytes(data):
            if byte == self.START:
                self.in_packet = True
            elif byte == self.STOP:
                self.in_packet = False
                self.handle_packet(bytes(self.buffer)) # make read-only copy
                del self.buffer[:]
            elif self.in_packet:
                self.buffer.extend(byte)
            else:
                self.handle_out_of_packet_data(byte)

    def handle_out_of_packet_data(self, data):
        """Process data that is received outside of packets"""
        pass
Benutzeravatar
Dennis89
User
Beiträge: 1578
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Antwort. Das heißt, dass macht man nicht so und ist keine Bibliothek von der ich mir etwas abschauen könnte.

Noch etwas anders, weil mir das auch wieder in diesem Zusammenhang aufgefallen ist. Ich programmiere ja nur zum Hobby und manche Bibliotheken "brauche" ich nur, wenn ich Code in Foren nachvollziehen will, von dem her kann ich mir einfach nicht alles merken und muss viel in der Doku nachschauen. Wäre es eigentlich nicht viel sinnvoller, wenn die Beispiele in der Doku gleich auch das `with`-Statement verwenden würden oder wenn wenigstens irgendwo darauf hingewiesen wird? Hier hatte ich es noch halb im Kopf und habe dann irgendwann die Basis-Klasse mit `__enter__` und `__exit__` gefunden. Ich meine auch andere Bibliotheken haben den Kontextmanager implementiert, aber weisen nicht darauf hin. Ja klar, kann ja jeder dokumentieren wie er will, ich dachte nur dass ist so eine sinnvolle Funktion, ich würde das auf jeden Fall erwähnen. (Vielleicht habe ich es auch überlesen oder in der Doku nicht gefunden, das will ich nicht ausschließen)


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14097
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Das kann schon Sinn machen in Bibliotheken wenn man bestimmte „hooks“ hat, die aber optional sind. Sirius3 ist jetzt von den abgeleiteten Klassen ausgegangen die wir *kennen*. Es kann ja durchaus sein, dass die noch andere implementieren wollen oder das beliebige *Benutzer* da später noch eigene Klassen von ableiten wollen, die mit den vorhandenen abgeleiteten Klassen weniger gemein hat.

`Protocol` ist als Begriff so etwas ähnliches wie „Interface“, das heisst diese Klasse dient auch als Dokumentation, die man für Interfaces in Python ja sonst nur informell macht/machen kann.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
DeaD_EyE
User
Beiträge: 1254
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Das hat schon einen Grund, dass die Basisklasse Methoden als Platzhalter hat, die durch Transport aufgerufen werden. Wenn man z.B. auf connection_made nicht reagieren will, muss man diese Methode noch nicht anlegen. Dann hat man z.B. keine Referenz zu Transport im Protocol. Will man hingegen einen Traceback bei connection_lost abfangen, muss man diese Methode implementieren, weil in der Basisklasse die Exception ausgelöst wird, ausgelöst durch Transport.

Wenn man hingegen in seiner Basisklasse Platzhalter anlegt und erzwingen will, dass die vererbende Klasse die Methoden implementiert, sind AbstractBaseClasses eine Anlaufstelle.

Code: Alles auswählen

from abc import ABC, abstractmethod


class Base(ABC):
    @abstractmethod
    def foo(self, x: int, y: int):
        pass

    @abstractmethod
    def bar(self):
        pass


class Foo(Base): pass


class Bar(Base):
    def foo(self):
        pass
    def bar(self):
        pass


for cls in (Bar, Foo):
    try:
        obj = cls()
    except TypeError as e:
        print(e)
    else:
        print(obj)
        obj.foo()

[deadeye@nexus ~]$ python abstract.py
<__main__.Bar object at 0x7f96a3336f90>
Can't instantiate abstract class Foo without an implementation for abstract methods 'bar', 'foo'
[deadeye@nexus ~]$ mypy abstract.py
abstract.py:26: error: Cannot instantiate abstract class "Foo" with abstract attributes "bar" and "foo" [abstract]
abstract.py:31: error: Missing positional arguments "x", "y" in call to "foo" of "Base" [call-arg]
Found 2 errors in 1 file (checked 1 source file)
PS: obj.foo() auskommentieren und mypy spuckkt einen Fehler weniger aus. Möglicherweise gibt es da eine bessere Lösung. TypeHints sind nicht einfach und oft stellt man sich selbst das Bein.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Sirius3
User
Beiträge: 18291
Registriert: Sonntag 21. Oktober 2012, 17:20

Es ist ja offensichtlich, dass eine Methode connection_made ein Transportobjekt setzt und ein connection_lost dieses Objekt wieder löscht. Und das reduziert auch einiges an kopiertem Code.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1254
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Sirius3 hat geschrieben: Samstag 23. August 2025, 07:12 Es ist ja offensichtlich, dass eine Methode connection_made ein Transportobjekt setzt und ein connection_lost dieses Objekt wieder löscht. Und das reduziert auch einiges an kopiertem Code.
Das ist der Denkfehler. Wenn ich connecion_made nicht verwenden will, weil ich keine Referenz zum Transport benötige, muss ich diese Methode nicht erstellen. Würde die Basisklasse z.B. bei connection_made automatisch den Transport dem Protokoll zuweisen, dann muss man die Methode erstellen, wenn man die Referenz nicht will.

Ja es gibt es Szenarien, wo man ausschließlich Daten bekommt und nichts senden kann, weil es keinen TX gibt.

Das hat schon seine Berechtigung und außerdem ist asyncio genauso gestaltet und ein weiterer Grund ist der Support für Asyncio. Ich hab schön öfters pySerial mit Asyncio genutzt. Das mit den Threads würde ich eher nicht nutzen.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
Dennis89
User
Beiträge: 1578
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

danke für eure Antworten.
__blackjack__ hat geschrieben: Freitag 22. August 2025, 22:29 Es kann ja durchaus sein, dass die noch andere implementieren wollen oder das beliebige *Benutzer* da später noch eigene Klassen von ableiten wollen, die mit den vorhandenen abgeleiteten Klassen weniger gemein hat.
Ich verstehe diesen Ansatz nicht. Für mich macht es Sinn wenn ich von einer Klasse erbe, wenn die Methoden hat, die auf mein Objekt auch zutreffen und das man dann nach belieben einzelne überschreibt. Ich sehe aber keinen Sinn von einer Klasse mit fast, ausschließlich leeren Methoden zu erben, weil dann muss ich die so oder so in meiner Klasse definieren. Der Ersteller der Klasse gibt mir so gesehen eigentlich nur eine Empfehlung für Methoden-Namen. `connectin_lost` hat etwas Code, ja, aber ja sehr allgemeingültig. (Sehe da übrigens viel Gemeinsamkeiten zu deinem Code von gestern, Zufall blackjack?)

Was verstehe ich hier noch nicht ganz?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
DeaD_EyE
User
Beiträge: 1254
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Wenn man es gut dokumentiert, muss man noch nicht einmal im Quellcode nachsehen: https://docs.python.org/3/library/async ... o-protocol

Für mich ist folgender Satz der wichtigste: Subclasses of abstract base protocol classes may implement some or all methods.

Es geht auch darum, anderen Entwickler die größtmögliche Freiheit zu gewährleisten und das geht nicht, wenn die Basis-Klassen zu restriktiv sind.
Anwender sollen erstmal den High-Level-Code verwenden und in Spezialfällen z.B. für UDP eine Protocol-Klasse erstellen.

Und falls man keine Lust hat sich mit dem Low-Level-Code zu beschäftigen, könnte man ChatGPT nach bekannten Modulen befragen. Hier ein Beispiel für UDP-Server, wo man normalerweise Protocol selbst implementieren müsste: https://chatgpt.com/share/68aada15-c1e0 ... 13f68b5e58

Ich würde dann z.B. dieses Paket nutzen: https://pypi.org/project/asyncio-dgram
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
nezzcarth
User
Beiträge: 1775
Registriert: Samstag 16. April 2011, 12:47

Dennis89 hat geschrieben: Sonntag 24. August 2025, 08:31 Der Ersteller der Klasse gibt mir so gesehen eigentlich nur eine Empfehlung für Methoden-Namen.
Nicht nur für die Methoden-Namen, sondern auch für die Signatur. Zudem besteht so im Code die Möglichkeit, die Beziehung nachzuvollziehen. Es gibt verschiedene etablierte Konzepte, die auf diesem Mechanismus (Basisklasse, die zum Überschreiben gedacht ist) aufbauen (z.B. SAX-Parser oder das Visitor-Pattern; für beides gibt es auch Beispiele in der Standardbibliothek: https://docs.python.org/3/library/xml.s ... entHandler https://docs.python.org/3/library/ast.h ... odeVisitor).
Benutzeravatar
Dennis89
User
Beiträge: 1578
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die weiteren Antworten.

Naja ich sag mal so, ich verstehe es etwas. Vielleicht kann man das auch besser nachvollziehen, wenn man selbst mal Teil von so größeren Objekten war.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten