Ich habe ein Python-Erweiterungsmodul, das ein Tupel als Attribut eines anderen Objekts erstellt und Elemente im Tupel setzt. Immer wenn ich dieses Modul in Python ausführe, erhalte ich den Fehler SystemError: bad argument to internal function
Nachdem ich die Dokumente für PyTuple
gelesen und mein Programm ein paar Stunden lang getestet hatte, konnte ich immer noch nicht herausfinden, was zur Hölle los war. Das Ausführen meines Programms durch einen Debugger zeigte an, dass das Problem innerhalb eines Bibliotheksaufrufs innerhalb des Python-Interpreters aufgetreten ist. So, schließlich habe ich mir den Python-Quellcode angeschaut, und endlich habe ich das Problem erkannt. Die Funktion PyTuple_SetItem
hat eine interessante Einschränkung, von der ich nichts wusste, und kann sie nicht explizit finden.
Hier ist die wichtige Funktion in der Python-Quelle (zur Verdeutlichung bearbeitet):
%Vor% Die wichtige Zeile hier ist die Bedingung op- & gt; ob_refcnt! = 1 . Hier ist das Problem: Sie können nicht einmal PyTuple_SetItem
aufrufen, es sei denn, das Tuple hat einen ref-count von 1. Es sieht so aus, als ob Sie nie PyTuple_SetItem
verwenden sollten, außer direkt nachdem Sie ein Tupel erstellt haben mit PyTuple_New()
. Ich denke, das macht Sinn, da Tupel ja eigentlich unveränderlich sein sollten, also hilft diese Einschränkung, Ihren C-Code besser mit den Abstraktionen des Python-Typ-Systems in Einklang zu bringen.
Allerdings kann ich diese Einschränkung nirgendwo dokumentiert finden. Relevante Dokumente scheinen hier und hier , von denen keine diese Einschränkung angibt. Die Dokumente sagen im Grunde, dass beim Aufruf von PyTuple_New(X)
alle Elemente im Tupel auf NULL
initialisiert werden. Da NULL
kein gültiger Python-Wert ist, ist es Aufgabe des Erweiterungsmodul-Programmierers sicherzustellen, dass alle Steckplätze im Tupel mit korrekten Python-Werten gefüllt sind, bevor das Tupel an den Interpreter zurückgegeben wird. Aber es sagt nirgendwo, dass dies getan werden muss, während das Tuple-Objekt eine Referenzzählung von 1 hat.
Nun, das Problem ist, dass ich mich im Grunde in eine Ecke eingeordnet habe, weil ich mir dieser (undokumentierten?) Beschränkung auf PyTuple_SetItem
nicht bewusst war. Mein Code ist so strukturiert, dass es sehr unpraktisch ist, Elemente in das Tupel einzufügen, bis nachdem das Tupel selbst zu einem Attribut eines anderen Objekts geworden ist. Wenn es also Zeit ist, die Elemente im Tupel zu füllen, hat das Tupel bereits eine höhere Ref-Zahl.
Ich werde wahrscheinlich meinen Code umstrukturieren müssen, aber ich habe ernsthaft darüber nachgedacht, den ref count auf dem Tuple nur vorübergehend auf 1 zu setzen, die Items einzufügen und dann die ursprüngliche ref count wiederherzustellen. Natürlich ist das ein schrecklicher Hack, ich weiß, und keine dauerhafte Lösung. Unabhängig davon würde ich gerne wissen, ob die Anforderungen bezüglich der Refs auf dem Tuple irgendwo dokumentiert sind. Ist es nur ein Implementierungsdetail von CPython oder ist es etwas, auf das sich API-Benutzer als erwartetes Verhalten verlassen können?
Ich bin mir ziemlich sicher, dass Sie die Einschränkungen umgehen können, indem Sie PyTuple_SET_ITEM
anstelle von PyTuple_SetItem
verwenden. PyTuple_SET_ITEM
ist ein in tupleobject.h
definiertes Makro wie folgt:
Also, wenn Sie absolut, definitiv und absolut sicher sind:
op
ist ein Tupel-Objekt i
im Tupel bisher noch nicht initialisiert v
und möchten, dass das Tupel es stehlen und PyTuple_SET_ITEM
aufrufen
Dann sind Sie sicher, dass Sie PyTuple_SET_ITEM
verwenden können.
Die Python-C-API ist sehr undokumentiert, und ich wäre nicht überrascht, wenn diese Einschränkung nirgends erwähnt würde .
Natürlich solltest du Tupel niemals modifizieren, wenn sie schon einmal etwas erreicht haben; Übergeben Sie entweder die Elemente, die Sie in das Tupel einfügen müssen, oder verwenden Sie stattdessen eine Liste.
Tags und Links python c tuples python-c-api