[C] Klassenfunktion aufrufen

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
theliquidwave
User
Beiträge: 221
Registriert: Sonntag 1. Juni 2008, 09:08

Hallo.
Wie realisiere ich es, dass ich auch Klassenfunktionen (Memberfunktionen, blabla) aufrufen kann? Also sowas:

Code: Alles auswählen

class X(object):
    def __init__(self):
        cmodule.register(self.func)
    
    def func(self, blah):
        print blah

def func_noclass(blah):
    print blah

cmodule.register(func_noclass)
Der Aufruf zu func_noclass funktioniert problem, X.func crasht aber.
Hier mal der Codeausschnitt: http://www.python-forum.de/pastebin.php?mode=view&s=12

Gibt es eigentlich sowas ähnliches wie PyEval_Check oder so? Ich konnte nichts dergleichen finden.

Edit: Gibt es eigentlich eine Möglichkeit, ein C-Modul wieder zu entladen?

Gruß
Grüßle.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Ich glaube, das ist ein Denkfehler deinerseits. `X.func` operiert doch immer AUF einem Objekt von `X` (welches per Konvention an den Namen `self` gebunden wird) -- wenn du jetzt also `X.func(42)` aufrufst, gibt es ja gar kein Objekt, auf dem die Methode operieren kann!

Vielleicht suchst du aber auch nur einfach sowas wie @staticmethod oder @classmethod.

Edit: `PyEval_Check`? Was soll das tun?
Edit2: "Entladen": Ich glaube, das geht, indem du alle Referenzen auf das Modulobjekt entfernst (also schlussendlich auch aus `sys.modules` entfernst). Aber warum willst du das?
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Ich weiß nicht, wieviele sinnvolle Antworten du dir daurauf erhoffst, aber zu "X.func crasht" kann man ohne den Quelltext zu sehen einfach gar nichts sagen (ohne zu raten). Bei C-Extensions ist beispielsweise die Referenzzählerei sehr wichtig, und davon sieht man exakt gar nichts.

Und der Fetzen Code, den du da zeigst, ist auch noch falsch: Der Rückgabewert von `Py_BuildValue` wird nicht überprüft und außerdem müsstest du explizit ein Tupel erstellen, wenn du das `PyEval_CallObject` übergibst (also "(s)" anstatt "s").
Chrisber hat geschrieben:Edit: Gibt es eigentlich eine Möglichkeit, ein C-Modul wieder zu entladen?
Nein.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Am schönsten, weil expliziter, finde ich da

Code: Alles auswählen

PyObject* args = PyTuple_Pack(/* size */ 1, ...);
if(args == NULL)
  stirb();
PyObject* rv = PyObject_CallObject(..., args);
/* Nicht vergessen: */
Py_DECREF(args);
theliquidwave
User
Beiträge: 221
Registriert: Sonntag 1. Juni 2008, 09:08

Hi.
@ Dauerbaustelle: Das ich nicht X.func registriere ist mir auch klar. Ich wollte damit nur zeigen, dass ich die Funktion von der Klasse X meine.
Das muss auch ohne staticmethod gehen...
PyEval_Check - damit meine ich ein Äquivalent zu ``callable``. Ich habe jetzt das hier gefunden:
http://docs.python.org/c-api/object.htm ... able_Check
Problem ist jedoch, dass mich der Satz dort verwirrt: "This function always succeeds." - heißt das, dass es immer 1 returnt?

@ Trundle: Ich bekomme die Callbackfunktion über PyArg_ParseTuple - muss ich da einen Reference Count ausführen?
Das mit dem Py_BuildValue wusste ich nicht, danke!

Gruß
Grüßle.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Chrisber hat geschrieben:@ Dauerbaustelle: Das ich nicht X.func registriere ist mir auch klar. Ich wollte damit nur zeigen, dass ich die Funktion von der Klasse X meine.
Das muss auch ohne staticmethod gehen...
Dafür ist static-/classmethod nunmal da. Wenn du das nicht verwenden willst musst du die Instanz/Klasse halt selbst übergeben.
Problem ist jedoch, dass mich der Satz dort verwirrt: "This function always succeeds." - heißt das, dass es immer 1 returnt?
Nein, das heißt, dass die Funktion immer funktioniert. Also du immer eine 0 oder 1 zurück kriegst und nichts anderes.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Chrisber hat geschrieben:Hi.
@ Dauerbaustelle: Das ich nicht X.func registriere ist mir auch klar. Ich wollte damit nur zeigen, dass ich die Funktion von der Klasse X meine.
Das muss auch ohne staticmethod gehen...
Dann zeig doch mal, was genau der Fehler ist, den du bekommst.
Problem ist jedoch, dass mich der Satz dort verwirrt: "This function always succeeds." - heißt das, dass es immer 1 returnt?
Return 1 if the object is callable and 0 otherwise
Ja, genau, es kommt immer 1 zurück.
Ich bekomme die Callbackfunktion über PyArg_ParseTuple - muss ich da einen Reference Count ausführen?
Du musst den Reference-Count erhöhen, wenn du `PyArg_ParseTuple` verwendest, ja. Trundle meinte aber was anderes.
theliquidwave
User
Beiträge: 221
Registriert: Sonntag 1. Juni 2008, 09:08

Hi.
Danke erstmal.

@ Dauerbaustelle: Fehler ist der sofortige Crash des Programms... Was soll ich dir da denn liefern?
Wenn ich den Reference Count erhöht habe, dann nach dem beenden der Funktion wieder einen "De"-Reference Count ausführen, richtig?

Gruß
Grüßle.
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Ein kleiner auf das Minimum reduzierter Testfall, der das Problem aufzeigt, wäre ganz, denn dann sieht man was du machst und muss nicht wild im Dunkeln rumstochern.

Prinzipiell erniedrigst du den Referenzzähler eben wieder, wenn du den Wert "wegschmeißt" - also beispielsweise, wenn deine `register`-Funktion einen neuen Callback speichert.

Oder du benutzt einfach Cython und machst dir um all das überhaupt keine Gedanken mehr.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
BlackJack

@Trundle: Refrenzzähler sind hart im Nehmen, die kann man nicht so leicht erniedrigen. Verringern kann man sie aber. ;-)
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Chrisber, gdb-Traceback zum Beispiel...
theliquidwave
User
Beiträge: 221
Registriert: Sonntag 1. Juni 2008, 09:08

Hi.
Ich habe es nun hingekriegt. Problem war, dass ich (durch einige Umstände im Code) die Klasse immer vorm Aufrufen gelöscht habe -.- (ja, ich schäme mich!)

Dennoch habe ich jetzt ein weiteres Problem: Wenn ich einen Callback einer statischen Funktion speichere (also den PyObject-Pointer), funktioniert das wunderbar (ich kann ihn später problemlos wiederfinden). Jedoch funktioniert das ganze nicht, wenn ich einen Callback auf eine Memberfunktion speichern will. Beispiel:

Code: Alles auswählen

myplugin.bla.hook("xblub", xblub) # Adresse von xblub z.B. 0x10a7bbf0
myplugin.bla.hook("instanceblub", instance.blub) # Adresse von instance.blub HIER z.B. 0x1029b1e8
# direkt danach
myplugin.bla.unhook("xblub", xblub) # Adresse von xblub ist immernoch 0x10a7bbf0 - kann ich als problemlos wiederfinden
myplugin.bla.unhook("instanceblub", instance.blub) # Adresse von instance.blub hat sich geändert: 0x1029b210
Problem ist nun natürlich, dass ich das nicht mehr wiederfinden kann, dementsprechend den Hook nicht entfernen kann - und das resultiert später in einem Fehler.

Hat da jemand eine Idee, wie ich das Problem lösen könnte? Gibt es da vielleicht so etwas wie PyInstance_IsInstance(<adresse vom hook>, <adresse vom unhook>) oder so?

Edit: In der API steht bei PyList_Append leider nichts davon, ob es einen neuen Reference Count erstellt oder nicht. Logisch wäre es natürlich. Was meint ihr dazu?

Gruß
Grüßle.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Eigentlich darf sich die Adresse nicht ändern. Warum sollte sie? Ich denke, das Problem sitzt wo anders :-)
theliquidwave
User
Beiträge: 221
Registriert: Sonntag 1. Juni 2008, 09:08

Naja. Was sollte hier denn falsch sein?

Code: Alles auswählen

import es, nativetools as nt

def load():
	nt.concommand.hook("maps", maps_hook)
	nt.concommand.hook("maps", maps_hook2)
	nt.concommand.hook("maps", instance.maps_hook3) // hier die Klasseninstanz
	nt.concommand.hook("status", status_hook)
	nt.server.registerMapEndCallback(map_end)

def unload():
	nt.concommand.unhook("maps", maps_hook)
	nt.concommand.unhook("maps", maps_hook2)
	nt.concommand.unhook("maps", instance.maps_hook3) // hier wird es dann nicht mehr gefunden da die Adresse anders ist
	nt.concommand.unhook("status", status_hook)
	nt.server.unregisterMapEndCallback(map_end)

def maps_hook(args):
	es.msg("Hook 1")
	return False

def maps_hook2(args):
	es.msg("Hook 2")
	return False

class mapsHook(object):
	def maps_hook3(self, args):
		es.msg("Hook 3")
		es.fail()
		return False

instance = mapsHook()

def status_hook(args):
	es.msg("Status Command executed!")
	es.msg("UserID: %i" % nt.concommand.getUserID())
	return False

def map_end():
	es.msg("Map ends now!")
Das ist das ganze Script (ungekürzt um Fehler nicht wegzuschneiden). Mein Plugin ist nativetools.

Gruß
Grüßle.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Ich meine im C-Programm.
BlackJack

@Chrisber: Du musst die Objekte vergleichen. `m` ist hier eine Methode:

Code: Alles auswählen

In [811]: a = A()

In [812]: a.m is a.m
Out[812]: False

In [813]: a.m == a.m
Out[813]: True
@Dauerbaustelle: Wenn man von einem Exemplar eine Methode abfragt wird da jedesmal ein *neues* Objekt zurückgegeben und das liegt halt nicht immer an der selben Adresse.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

BlackJack, danke, hatte ich noch nicht gewusst -- aber die `id(meth)` sind gleich? Dann hab ich wohl `id` falsch verstanden...
BlackJack

@Dauerbaustelle: Nein die `id()`-Werte sind nicht gleich. Wären sie gleich würde ``is`` ja `True` ergeben.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

BlackJack hat geschrieben:@Dauerbaustelle: Nein die `id()`-Werte sind nicht gleich. Wären sie gleich würde ``is`` ja `True` ergeben.
Seltsam :-/

Code: Alles auswählen

In [1]: class Foo(object):
   ...:     def meth(self):
   ...:         pass
   ...:     

In [2]: foo = Foo()

In [3]: foo.meth is foo.meth
Out[3]: False

In [4]: id(foo.meth) == id(foo.meth)
Out[4]: True
BlackJack

@Dauerbaustelle: In beiden Fällen hat man einen binären Operator bei dem erst das linke und dann das rechte Argument ausgewertet wird.

Fall 1: Links: ``foo.meth`` erzeugt ein aufrufbares Objekt an einer bestimmten Adresse. Rechts: ``foo.meth`` erzeugt ein anderes aufrufbares Objekt an einer anderen Adresse. ``is`` vergleicht die Identität der beiden unterschiedlichen Objekte.

Fall 2: Links: ``id(foo.meth)`` erzeugt ein aufrufbares Objekt an einer bestimmten Adresse, übergibt es an die `id()`-Funktion, die liefert eine Zahl, das aufrufbare Objekt wird nicht mehr gebraucht und der Speicher wird wieder freigegeben. Rechts: ``id(foo.meth)`` erzeugt ein aufrufbares Objekt an der eben gerade freigewordenen Adresse an der auch das andere Objekt lag. Die `id()`-Funktion liefert die gleiche Zahl. ``==`` ergibt bei gleichen Zahlen konsequenterweise `True`.

`id()` garantiert nur unterschiedliche Werte bei Objekten die gleichzeitig existieren. Andererseits darf man sich auf das beobachtete Verhalten auch nicht verlassen, denn das CPython für die `id()`-Funktion die Adresse liefert ist ein Implementierungsdetail. Die Implementierung könnte genausogut jedem erzeugten Objekt eine "Seriennummer" verpassen die hochgezählt wird. Jython macht das zum Beispiel so wenn ich mich recht erinnere.
Antworten