Pseudoüberladung

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
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Ich möchte für eine __init__-Funktion zwei mögliche Signaturen anbieten:
  • GLShader(shader_type:GLenum, source:string)
  • GLShader(identifier:GLuint)
Mein erster Gedanke war, dieses mittels *args zu realisieren:

Code: Alles auswählen

    def __init__(self, *args):
        assert len(args) in (1, 2)
        super().__init__()
        if len(args) == 1:
            self.identifier = args[0]
        else:
            self.identifier = _create_shader(shader_type)
            self._set_shader_source(source)
            self._compile_shader()
Ich werde das Gefühl jedoch nicht los, dass das nicht ganz im Sinne des (Python-)Erfinders ist. Auf statische Factories wollte ich gerne verzichten, da ich dann eine __init__ mit drei Argumenten bereitstellen müsste und am Ende doch wieder diese abprüfen müsste.

Wie geht man dies in der Regel pythonisch an?

Grüße ... bwbg
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
BlackJack

@bwbg: Ich weiss nicht ob's pythonisch ist, oder überhaupt ein guter API-Entwurf, aber:

Code: Alles auswählen

    def __init__(self, type_or_identifier, source=None):
        super().__init__()
        if source is None:
            self.identifier = type_or_identifier
        else:
            self.identifier = _create_shader(type_or_identifier)
            self._set_shader_source(source)
            self._compile_shader()
Wobei ich nicht sehe wieso man bei einer Klassenmethode eine `__init__()` mit drei Argumenten braucht‽

Edit:

Code: Alles auswählen

    def __init__(self, identifier):
        super().__init__()
        self.identifier = identifier

    @classmethod
    def from_source(cls, shader_type, source):
        result = cls(cls._create_shader(shader_type))
        result._set_shader_source(source)
        result._compile_shader()
        return result
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Ich wusste doch, dass ich mich auf Dich verlassen kann ;)

classmethod habe ich ganz verdrängt. Empfinde ich am saubersten und kommt der CAPI noch am nächsten.

Grüße ... bwbg
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Kann es sein, dass `GLShader(shader_type:GLenum, source:string)` den Shader von grundauf neu erstellt und `GLShader(identifier:GLuint)` anhand des Identifiers einen Shader aus dem Cache nutzt? In diesem Fall würde ich eher eine `Shader.for_id()` anbieten und die erstgenannte Signatur für `__init__()` nehmen. Sähe zumindest für mich etwas sauberer aus.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Ich habe mich nun für die folgende Strukturierung entschieden:

Code: Alles auswählen

class GLObject:
    def __init__(self, identifier):
        self.identifier = identifier
        
    @property
    def _as_parameter_(self): # For convenience with ctypes
        return self.identifier
        
    @classmethod
    def for_id(cls, identifier):
        result =  cls.__new__()
        result.identifier = identifier
        return result

        
class GLShader(GLObject):
    def __init__(self, shader_type, source):
        pass

    # ...
Da alle (mir bisher bekannten) Objekte in OpenGL sich mittels eines GLuint/identifiers verwenden lassen, habe ich eine entsprechende Basisklasse definiert, welche eine Klassenmethode for_id bereitstellt.

Im Grunde werden alle (künftigen) von GLObject abgeleiteten Klassen lediglich den identifier als inneren Zustand besitzen. Alle weiteren Funktionalitäten plane ich durch properties und Methoden bereitzustellen.

Grüße ... bwbg

PS. Ja, ich weiß, dass ess Projekte wie PyOpenGL und pyglet gibt. Es ist ein Lernprojekt, um mich mit OpenGL und nebenbei mit ctypes vertraut zu machen ;)
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich kenne mich mit OpenGL nicht wirklich aus, würde aber erwarten, dass bei Angabe des Identifiers eher ein bereits erstelltes Objekt aus einem Cache zurückgeliefert wird. Als Minimum würde ich zumindest erwarten, dass ich nicht munter neue Typen für bereits verwendete Identifier angeben kann. Du könntest dir bereits erstellte Objekte evtl in einem *Klassen*attribut von `GLObject` als Dictionary merken und `.from_id()` diese zurückliefern lassen. Letzteres würde ich dann auch eher als `@staticmethod` definieren. Dein jetziger Code sieht mehr nach nem Hack aus, der irgendwie die Tatsache umschiffen muss, dass `@classmethod`s eigentlich so gedacht sind, dass tatsächlich Objekte der jeweiligen Klasse erzeugt werden und nicht etwa des Basistyps. Ich lehne mich mal soweit aus dem Fenster, dass ich das Design in seiner jetzigen Form schon fast als kaputt ansehen würde. Außer natürlich, falls ich den Sinn dieser Identifier völlig fehlinterpretiert habe. Dann will ich nichts gesagt haben... :)

EDIT: Um's nochmal deutlich zu sagen: Falls es darum geht, neu erstellte Objekte mit Kennungen zu versehen, dann würde ich in den Signaturen der verschiedenen Typen durchaus den `identifier` als letztes Argument mitschleppen. Ich weiß z.B. von Qt, dass man da auch in vielen Signaturen ein `parent`-Argument am Ende findet (auch wenn ich hier sicherlich Äpfel mit Birnen vergleiche). Wenn aber auf bereits erstellte Objekte über ihren jeweiligen Identifier zugegriffen werden soll, dann wäre eine statische Methode - oder sogar eine simple Funktion auf Modulebene - besser. Ich hoffe einfach mal, wir reden jetzt nicht zu sehr aneinander vorbei. Vielleicht willst du ja ein paar Erläuterungen zur Verwendungsweise dieser Identifier geben...
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier mal ein Beispiel, das in etwa zeigen soll, wie ich das meinte:

Code: Alles auswählen

class GLObject(object):
    _cache = {}

    def _register(self, identifier):
        if identifier is not None:
            self._cache[identifier] = self

    @classmethod
    def from_id(cls, identifier):
        obj = cls._cache[identifier]
        if not isinstance(obj, cls):
            msg = 'requested object is not of type %r' % cls.__name__
            raise TypeError(msg)
        return obj


class GLShader(GLObject):
    def __init__(self, shader_type, source, identifier=None):
        self._register(identifier)
        self.shader_type = shader_type
        self.source = source


class GLFoo(GLObject):
    def __init__(self, foo, bar, identifier=None):
        self._register(identifier)
        self.foo = foo
        self.bar = bar
Anwendung:

Code: Alles auswählen

>>> from gltest import GLShader, GLFoo
>>> shader = GLShader("DEFAULT_SHADER", "source", "myshader")
>>> shader_from_id = GLShader.from_id("myshader")
>>> shader_from_id is shader
True
>>> shader_from_id = GLFoo.from_id("myshader")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "gltest.py", line 13, in from_id
    raise TypeError(msg)
TypeError: requested object is not of type 'GLFoo'
Wie du siehst, simuliere ich hier mehr oder weniger statische Typisierung. Viellicht möchte man das bei einem Wrapper ja so haben. Es ist natürlich auch immer die Frage, wie weit so ein Wrapper gehen sollte in Bezug auf sein eigenes Verhalten...
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Vielleicht habe ich den Begriff "Identifier" auch einfach falsch gewählt. Im Grunde ist es nichts weiter als eine Ganzzahl, welche ein Objekt im OpenGL-Kontext kennzeichnet (ähnlich einer Speicheradresse oder ein Handle).

Eine statische Typisierung, wie von Dir angemerkt benötige ich auf der Python-Seite nicht. Die OpenGL-Funktionen prüfen ihrerseits, ob der Identifier/Adresse/Handle für die jeweilige Operation gültig ist.

Frei nach dem Motto "Quelltext sagt mehr als tausend Worte", ein erster ausbaufähiger Gehversuch:

https://gist.github.com/anonymous/5391641

Es ist schon spät und meine bessere Hälfte wartet ... :wink:

Grüße ... bwbg
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Na gut, das sieht ja schon ein bißchen anders aus. Ich würde jedoch den `ctypes`-spezifischen Code in ein gesondertes Modul packen. Dieses Modul würde dann die unterste Schicht darstellen, welche den tatsächlichen Übergang zwischen Python und C realisiert. Darauf aufbauend würde ich die Abstraktionen machen.

EDIT: `GLError.raise_if_any()` finde ich als Aufruf zu kompliziert. Da würde ich wohl eher eine Funktion auf Modulebene namens `check_errors()` oder so verwenden. `check_errors()` könnte dann seinerseits wieder eine Liste von zu erwartenden Fehlertypen annehmen, womit man sich die Angabe als Kommentar sparen könnte. Im Grunde läuft das aber schon wieder auf Exceptions hinaus, die man einfach mittels `try`-`except` behandelt. Diese würden dann von dem oben vorschlagenen auf `ctypes`-Ebene agierenden Modul geworfen werden.
Antworten