"Unterklasse" mit Zugang zu Variablen der äußeren Klasse?

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
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hi,

erst mal Entschuldigung für die etwas komische Überschrift, aber es fehlt mir die Terminologie um das Problem besser zu beschreiben.
Habe folgendes Problem.
Ich schreibe gerade immer noch an meinem wrapper für eine DLL.
Dabei geben die aufgerufenen Funktionen einen Fehlercode zurück wenn ein Fehelr auftrat und 0 wenn alles okay ist.
Die DLL hat auch eine Funktion dabei um diesen Fehlercode in einen Text, also Menschen lesbar, zu verwandeln.
Ich habe mir nun eine kleine Errorklasse gebastelt die dies erledigen soll:

Code: Alles auswählen

import ctypes
class mca:
    class ErrorCode(Exception):
        self.DecodeError = getattr(self.dll,"nako_DecodeError")
        def __init__(self, errcode):
            self.errcode = errcode
        def __str__(self):
            return self.DecodeError(self.errcode)

    def __init__(self):
        boardnumber = 0
        self.dll = ctypes.windll.nakolib
        self.handle = ctypes.c_int32(0)
        errcode = getattr(self.dll,"_nako_Init@8")(ctypes.c_short(boardnumber), ctypes.byref(self.handle))
        if not errcode == 0:
            raise self.Errcode(errcode)

Das Problem ist, wie bekommt ErrorCode den richtigen Bezug auf self.dll? Den self bezieht sich ja in ErrorCode auf ErrorCode und nicht auf mca. Super kann ich auch nicht verwenden, ist ja nicht vererbt. Bei jedem Aufrufen diese Referenz mit zu liefern ist umständlich. Also was tun? Einfach nochmal die DLL aufmachen? Und selbst wenn dies hier ginge, ich bin schon mehrfach auf ein solches Problem gestoßen. Wäre also super wenn es eine einfache Lösung dafür gäbe und ich einfach zu blöd bin es zu sehen ;)

Bis später


p90
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

Vergiss am besten deinen Ansatz und spare dir den Weg über die ErrorCode-Exception. Diese sollte nicht dafür verantwortlich sein den Fehler zu extrahieren, sondern nur den Fehler repräsentieren. Ich würde in `mca` ein Methode erstellen, welche die entsprechende Information abruft. Damit kannst du dann deine eigene Exception werfen.

Du solltest die auch gedanken über vernünftige Namen machen. `mca` und `ErrorCode` sagen nicht besonders viel aus.

Sebastian

Edit: Fast übersehen:

Code: Alles auswählen

if not errcode == 0:
    ...

if errcode:
    ....
Das Leben ist wie ein Tennisball.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

p90 hat geschrieben:Das Problem ist, wie bekommt ErrorCode den richtigen Bezug auf self.dll?
Überhaupt nicht, weil ohne Instanziierung kein 'self'.

Code: Alles auswählen

class Main(object):
    class_ = 'class main'
    class Sub(object):
        class_ = 'class sub'
        def __init__(self):
            self.self_ = 'instanz sub'

    def __init__(self):
        self.self_ = 'instanz main'
        self.sub = self.Sub()

Code: Alles auswählen

In [53]: m = Main()

In [54]: m.class_
Out[54]: 'class main'

In [55]: m.Sub.class_
Out[55]: 'class sub'

In [56]: m.self_
Out[56]: 'instanz main'

In [57]: m.sub.self_
Out[57]: 'instanz sub'
mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

@ EyDu
Wegen den Namen, entschuldigt bitte, hatte nicht daran gedacht das euch mca(multi-channel-analyser) ja nichts sagt aber hier, denke ich, ist das schon okay.
Wenn ich einen wrapper zum ansprechen eines mca's schreibe darf das auch mca heißen.

Deine Lösung verstehe ich gerade noch nicht ganz. Also ich bekomme halt nur eine Fehlernummer (halt den errorcode) und möchte jetzt nicht dem User sagen "Fehler 5" sondern
"Das Handle ist nicht mehr verfügbar" wobei ich selber nicht alle möglichen Fehlercodes kenne sondern diese nur der DLL entnehmen kann.
Du sagt jetzt, nimm keine ErrorCode Exception für alle Fehler sondern mach dir für jeden Fehler eine eigene Exception? Aber ich kenne die Fehler ja gar nicht sondern kann sie mir von
der DLL sagen lassen?

@mutetella
Also es gibt keinen Weg das zu bewerkstelligen außer bei jedem Initialisieren den Parameter mitzugeben?
Man kann da nicht der Klasse eine Variable mitgeben die dann beim Initialisieren verwendet wird?
BlackJack

@p90: Ich würde die DLL und die Funktionen nicht innerhalb der Klasse sondern in einem Modul definieren und dann eine Klasse, die diese Funktionen benutzt.

Ferner würde ich bei den Funktionen, die `ctypes` aus der DLL holt, auch die Typinformationen für die Argumente und die Rückgabewerte setzen. Man kann dort auch gleich für die Funktionen, die einen Fehlerwert liefern einen Handler setzen, der diesen in eine Ausnahme umwandelt. Dann muss man das nicht bei jedem Aufruf prüfen.

Ist das ``@8`` bei ``_nako_Init`` tatsächlich nötig? ``dll._nako_Init`` funktioniert nicht?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

p90 hat geschrieben:Deine Lösung verstehe ich gerade noch nicht ganz. Also ich bekomme halt nur eine Fehlernummer (halt den errorcode) und möchte jetzt nicht dem User sagen "Fehler 5" sondern
"Das Handle ist nicht mehr verfügbar" wobei ich selber nicht alle möglichen Fehlercodes kenne sondern diese nur der DLL entnehmen kann.
Nein, du sollst eine Funktion schreiben, welche sich mittels eines Fehlercodes (Zahl nicht die Klasse) die entsprechende Fehlermeldung aus der DLL holt. Anschließend übergibst du die Fehlermeldung an deine eigene Exception. Damit der Fehler unabhängig vom Zugriff auf die DLL. Dann kannst du auch gleich die ErrorCode-Klasse aus der mca-Klasse ziehen.
p90 hat geschrieben:Also es gibt keinen Weg das zu bewerkstelligen außer bei jedem Initialisieren den Parameter mitzugeben?
Man kann da nicht der Klasse eine Variable mitgeben die dann beim Initialisieren verwendet wird?
Richtig, das Problem kannst du in allgemeiner Form nur mit Parametern lösen. Wenn es nur eine Instanz von mca gäbe, dann würde es auch anders gehen, das will man aber gar nicht ;-)
Das Leben ist wie ein Tennisball.
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

@BlackJack
Ja, die @8 ist notwendig, sonst bekomme ich nur ein "Attribut not found".
Ich vermute sie haben das in der DLL angehängt um zu zeigen wie viel Bytes die Funktion als Argument habe will.
Aber die Spezifikation schweigt sich dazu aus.

Das ich das später in einem Modul habe ist auch so geplannt, immo habe ich aber erst ca. 50% der vorhandenen Funktionen vernünftig eingebaut, der Rest liegt noch in der Form

Code: Alles auswählen

Funktion = getattr(dll, "_Funktion@16")
vor. Deshalb habe ich in meinem Modul gerade noch zwei Klassen, mca für die die bereits python gerecht sind und todo für die bei denen man alles selber machen muss.
Da meine vorhandene Spezifikation etwas mau ist, muss ich halt immer direkt am Gerät sehen was passiert, deshalb die Klasse todo mit der ich nach Herzenslust experimentieren kann und dann die Funktionen nach mca bringe.

Das mit dem argtypes sollte ich wirklich noch einbauen, bin mir aber nicht sicher wie dann die Konvertierung funktioniert.
Also so:

Code: Alles auswählen

def __init__(self):
        boardnumber = 0
        self.dll = ctypes.windll.nakolib
        self.handle = 0
        function = getattr(self.dll,"_nako_Init@8")
        function.argtypes = (c_short, ctypes.POINTER(c_int) )
        errcode = function(boardnumber, self.handle)
        if not errcode == 0:
            raise self.Errcode(errcode)

Aber dann muss ich doch immer noch ein byref für self.handle machen oder?
Und welchen typ hat den self.handle dann? c_int oder ist es immer noch ein pyobject?

Das letzte mit dem Handle verstehe ich nicht ganz, das musst du mir etwas näher erklären, google hat mir da jetzt nur was zum Signal handling zurück geliefert.

@EyDu
Diese Funktion habe ich ja, nur das sie halt eine Funktion der DLL ist. Aber was du, glaube ich, meinst, ist, (oh man was für ein Satz) dass ich die Exception nicht nach der Fehlernummer werde sondern direkt eine exception mit dem entsprechendem Namen werde?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Mal als Code:

Code: Alles auswählen

class ErrorCode(Exception):
    pass

class mca:
    def __init__(self):
        boardnumber = 0
        self.dll = ctypes.windll.nakolib
        self.handle = ctypes.c_int32(0)
        errcode = getattr(self.dll,"_nako_Init@8")(ctypes.c_short(boardnumber), ctypes.byref(self.handle))
        if errcode:
            raise ErrorCode(code_to_message(errcode))
    
    def code_to_message(self, code):
        return self.dll.nake_DecodeError(code)
Das Leben ist wie ein Tennisball.
BlackJack

@p90: Die ``@8`` wird vom Compiler/Linker automatisch angehängt und sollte von `ctypes` aber auch genau so automatisch wieder entfernt werden. Vielleicht nicht wenn man `getattr()` verwendet, aber das ist ja auch auch ein wenig ungewöhnlich.

Wenn Du das immer machen musst, dann läuft da irgend etwas falsch. Genau so verwundert mich der führende Unterstrich.

Deine Anmerkungen zu den Klassen verstehe ich nicht!? Und wozu ist die Todo-Klasse nötig? Warum schreibst Du das 50% der Funktionen *noch* auf Modulebene vorliegen? Da sollten IMHO *alle* definiert werden. In Klassen sollten IMHO keine Attribute von der DLL abgefragt werden, sondern nur Funktionen verwendet werden, die auf Modulebene *einmal* definiert wurden. Und dort in der Form:

Code: Alles auswählen

_funktionsname = dll.Funktionsname
# TODO: Argumenttypen, Rückgabetyp, Testfunktion, ... an die Funktion binden.
Falls die Funktion auch für die öffentliche API des Moduls nützlich ist, sollte man den führenden Unterstrich natürlich weglassen.

Handler ist englisch für eine Funktion die etwas behandelt, also ganz allgemein. Man kann für `ctypes`-Funktionen eine Funktion festlegen, welche den Rückgabewert als Argument bekommt. Und da kann man dann Umwandlungen oder Prüfungen vornehmen und gegebenenfalls eine Ausnahme auslösen.

Ich würde das so aufbauen (ungetestet):

Code: Alles auswählen

from ctypes import byref, c_char_p, c_int, c_int32, c_short, POINTER, windll


class Error(Exception):
    def __init__(self, error_code, message):
        Exception.__init__(message)
        self.error_code = error_code


def _error_code_check(result, func, _arguments):
    if result != 0:
        raise Error('%s = %s' % (func.__name__, _decode_error(result)))

nako_dll = windll.nakolib

_decode_error = nako_dll.nako_DecodeError
_decode_error.argtypes = []
_decode_error.restype = c_char_p

_init = nako_dll.nako_Init
_init.argtypes = [c_short, POINTER(c_int32)]
_init.restype = c_int
_init.errcheck = _error_code_check


class MCA(object):
    def __init__(self):
        self.handle = c_int32()
        _init(0, byref(self.handle))
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

@BlackJack

Erst mal Danke!
Das ist genau das was ich hier vewenden sollte.
Das mit dem .errcheck ist einfach super, hatte es leider in der Doku überlesen (es erschlägt einen etwas wenn man davon keine Ahnung hat ^^)

Zum dem anderen:
Ich werde es nochmal nur mit dll._function probieren aber das letzte mal hat er dann die Funktion nicht gefunden und nachdem ich dann mit einem DLL-Explorer in die DLL geschaut hatte hab ich es dann mit getattribut gemacht und es ging. Schönere wäre es natürlich ohne ^^

Was ist den genau der Grund warum man die Funktionen auf der modulebene definiert? Wollte das eigentlich vermeiden da ich es für keine gute Idee hielt da Funktionen einzumischen die man mit ctypes statt normalen pyobjects füttert?

MfG und vielen vielen Dank für die Hilfe


p90
BlackJack

@p90: Also meine Begründung wäre, dass das in DLLs Funktionen sind und man Funktionen in Python auf Modulebene definiert. Du scheinst die ja für jeden Methoden-Aufruf ad hoc jedes mal aufs neue zu erstellen. Das wird IMHO unübersichtlich. Die Methoden werden dadurch umfangreicher als sie müssten und wenn man eine Funktion in mehreren Methoden benötigt, dann hätte man den Code zum erstellen der Funktion auch noch mehrfach im Quelltext stehen.

Ich habe den `ctypes`-Funktionen in dem Beispiel ja einen Unterstrich vorangestellt um sie als Implementierungsdetail zu kennzeichnen. Für den Benutzer des Moduls sind die Funktionen, die `ctypes`-Typen entgegen nehmen damit ja gar nicht gedacht. Wenn sie in dem Modul stören, könnte sie man ja auch in ein eigenes Modul auslagern.

Mit dieser Trennung: erst die Funktionen als Python-Funktionen zumindest intern für das Modul verfügbar zu machen und dann daraus Python-Klassen zu erstellen, bin ich bisher immer ganz gut gefahren. Man kann die DLL-Anbindung mit den Funktionen testen, ob die sich genau so verhalten wie ein entsprechendes C-Programm, ohne dass man den Teil mit testen muss, der die API ”pythonischer” machen soll. Und der ”pythonische Zuckerguss” ist schlanker.

Kann es sein, dass Du am Anfang die DLL nicht als Windows-DLL eingebunden hast? Das '@' mit der Zahl der Bytes für die Argumente ist nämlich eine Windows-Geschichte. Etwas was bei C noch so aussieht: ``int foo(int x)`` kann in der Windows-DLL intern unter dem Namen ``_foo@4`` stehen. Aber soweit ich weiss sollte man trotzdem mit `ctypes` da trotzdem mit ``dll.foo`` dran kommen können. Kann mich aber auch irren, denn Windows ist nicht so meine Baustelle.
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hi,

ja das stimmt.
Als ich angefangen habe, habe ich versucht die Funktionen mit cdll zu laden (cdecl calling convention), musste das ganze dann um überhaupt an die Funktionen zu kommen getattrib benutzen, habe dann festgestellt, das es die falsche Convention ist und habe dann windll (stdcall calling convention) getestet mit der es dann ging. Du lagst mit der Annahme also richtig.
Antworten