2012-10-25 5 views
5

Ich versuche nur zu verstehen, wie man mit den Referenzzählungen umgehen muss, wenn man die Python C API benutzt.Python C API, hohe Anzahl von Referenzen für neues Objekt

Ich möchte eine Python-Funktion in C++ nennen, wie folgt aus:

PyObject* script; 
PyObject* scriptRun; 
PyObject* scriptResult; 

// import module 
script = PyImport_ImportModule("pythonScript"); 
// get function objects 
scriptRun = PyObject_GetAttrString(script, "run"); 
// call function without/empty arguments 
scriptResult = PyObject_CallFunctionObjArgs(scriptRun, NULL); 

if (scriptResult == NULL) 
    cout << "scriptResult = null" << endl; 
else 
    cout << "scriptResult != null" << endl; 

cout << "print reference count: " << scriptResult->ob_refcnt << endl; 

Der Python-Code in pythonScript.py ist sehr einfach:

def run(): 
    return 1 

Die Dokumentation "PyObject_CallFunctionObjArgs" sagt, Sie erhalten eine neue Referenz als Rückgabewert. So würde ich „Script“ erwartet einen Referenzzähler von 1 zu haben, aber der Ausgang ist:

scriptResult != null 
print reference count: 72 

Außerdem würde ich einen Speicherverlust erwarten, wenn ich dies in einer Schleife tun würde, ohne den Referenzzähler abnimmt. Dies scheint jedoch nicht zu geschehen.

Könnte jemand mir helfen zu verstehen?

Mit freundlichen Grüßen!

+0

Eine weitere Frage: Dank @ecatmur und @KayZhu verstehe ich jetzt, warum es kein Speicherleck gibt. Trotzdem, wenn ich diesen Code in einer langen Schleife laufen lasse, zerquetscht mein vollständiges Betriebssystem sowieso. Die Referenzzählung auf "1" nimmt mit jeder Iteration zu, aber ich sehe nicht, warum dies zu einem Systemfehler führen sollte. – user1774143

+0

Schleifen Sie, bis "ob_refcnt" durch 0 zurückläuft? Die Referenzzahl von 1 schwankt stark. Wenn Sie nach 0 fortfahren, könnten normale Operationen 'Py_DECREF' auf 0 setzen und dazu führen, dass 'int'1 freigegeben wird, gefolgt von einem segfault. Versuchen Sie es mit einem weniger häufig internierten "Int" wie 13. – eryksun

+0

Zumindest war ich nicht bis "sys.maxint" (das ist "9223372036854775807" auf meinem System). Heute scheint es, dass ich den Fehler nicht reproduzieren kann und ich denke, ich sollte aufhören, meinen Arbeitstisch zu erschießen. Danke für Ihre Hilfe! – user1774143

Antwort

2

Ecatmur ist richtig, Zahlen und Strings sind in Python interniert, also können Sie stattdessen mit einem einfachen object() Objekt versuchen.

Eine einfache Demo mit gc:

import gc 


def run(): 
    return 1 

s = run() 
print len(gc.get_referrers(s)) # prints a rather big number, 41 in my case 

obj = object() 
print len(gc.get_referrers(obj)) # prints 1 

lst = [obj] 
print len(gc.get_referrers(obj)) # prints 2 

lst = [] 
print len(gc.get_referrers(obj)) # prints 1 again 

Ein bisschen mehr: wenn CPython ein neues Objekt erstellt, ruft ein C Makro _Py_NewReference den Referenzzähler zu initialisieren 1. Dann Py_INCREF(op) und Py_DECREF(op) verwendet um die Referenzzahl zu erhöhen und zu verringern.

+0

Danke! Wenn ich jedoch s durch eine Zahl wie 123456.7565 ersetze, bekomme ich immer noch 1 Referrer. Wenn ich es in meinem Python-Code mache, der von meinem C++ - Code aufgerufen wird, bekomme ich immer noch 2. Irgendeine Idee, warum das so sein könnte? – user1774143

+1

@ user1774143: Gibt '123456.7565' von der Funktion' run() 'zurück? Wenn dies der Fall ist, ist die zweite Referenz das Tupel der Konstanten des Codeobjekts: 'run .__ code __. Co_costs'. – eryksun

+0

Ah verstehe ich. In diesem Tupel sind alle Konstanten innerhalb der Methode gespeichert, so dass ich hier eine zusätzliche Referenz habe. Vielen Dank! – user1774143

4

Die Verwirrung ist, dass kleine ganze Zahlen (auch True, False, None, Single-Zeichenketten, etc.) sind interniert ("is" operator behaves unexpectedly with integers), was bedeutet, dass überall dort, wo sie verwendet werden oder in einem Programm erhalten die Laufzeit wird versuchen, die gleiche Objektinstanz zu verwenden:

>>> 1 is 1 
True 
>>> 1 + 1 is 2 
True 
>>> 1000 + 1 is 1001 
False 

das bedeutet, dass, wenn Sie schreiben return 1, sind Sie eine bereits bestehende int Objektinstanz mit der Rückkehr einen beträchtlichen Referenzzählers (wie Sie gesehen haben). Da die gleiche Instanz an anderer Stelle verwendet wird, führt ein fehlendes Dereferenzieren nicht zu einem Speicherverlust.

Wenn Sie Ihr Skript in return 1001 oder return object() ändern, sehen Sie eine anfängliche Referenzanzahl von 1 und ein Speicherleck.

+0

Vielen Dank für die schnelle Antwort, wusste ich nicht über die kleine ganze Sache! Wenn ich jedoch den Rückgabewert auf 1001 oder einen anderen lustigen Wert ändere, erhalte ich immer noch eine Referenzzählung von 2 anstelle von 1. – user1774143

+0

Okay, ich sehe jetzt, dass für das Zurückgeben von object() der Referenzzähler tatsächlich 1 ist! – user1774143

Verwandte Themen