Seite 1 von 1
Pseudoüberladung
Verfasst: Sonntag 14. April 2013, 22:05
von bwbg
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
Re: Pseudoüberladung
Verfasst: Sonntag 14. April 2013, 22:15
von 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
Re: Pseudoüberladung
Verfasst: Sonntag 14. April 2013, 22:32
von bwbg
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
Re: Pseudoüberladung
Verfasst: Montag 15. April 2013, 07:22
von snafu
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.
Re: Pseudoüberladung
Verfasst: Montag 15. April 2013, 15:44
von bwbg
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

Re: Pseudoüberladung
Verfasst: Montag 15. April 2013, 21:49
von snafu
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...
Re: Pseudoüberladung
Verfasst: Montag 15. April 2013, 22:34
von snafu
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...
Re: Pseudoüberladung
Verfasst: Montag 15. April 2013, 23:07
von bwbg
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 ...
Grüße ... bwbg
Re: Pseudoüberladung
Verfasst: Montag 15. April 2013, 23:31
von snafu
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.