Wenn man händisch CPython-Erweiterungen mit Hilfe der C-API baut (und
nicht etwa Cython oder Ähnliches benutzt), läuft man leider sehr
leicht Gefahr, dass man versehentlich Reference Leaks einbaut.
Um das Auffinden von Refleaks zu erleichtern, zählt CPython bei einem
Debug-Build die Summe aller Referenzen:
Python 3.4.0a0 (default:9214f8440c44, Nov 11 2012, 00:15:25)[GCC 4.7.1] on linuxType "help", "copyright", "credits" or "license" for more information.>>>[60488 refs]>>> n=42[60490 refs]
Die Summe der Referenzen ist die Zahl zwischen den []. In diesem
Beispiel hat sich die Gesamtanzahl der Referenzen um zwei erhöht, weil
die Zahl 42 an zwei Namen gebunden wurde: einmal an n und einmal
an _, weil Python im interaktiven Modus immer unter _ eine
Referenz auf den letzten Wert hält.
Die Gesamtanzahl der Referenzen kann man in Debug-Builds auch
programmatisch über die Funktion sys.gettotalrefcount erhalten. Also
liegt es nahe, dass man versucht, Refleaks automatisch zu finden,
beispielsweise, indem man die Gesamtanzahl der Referenzen vor und nach
einem Testlauf betrachtet.
Die Idee ist hierbei recht einfach: Wird die Test-Suite mit einem
Debug-Python ausgeführt, wird jeder Test fünf mal ausgeführt. Bei den
letzten zwei Durchläufen wird dann die Gesamtzahl der Referenzen vor
und nach dem Test verglichen. Die drei Durchläufe vorher werden nicht
ausgewertet, damit sich die Zahl der Referenzen erst auf einen Wert
einpendeln kann (beispielsweise, wenn Caches im Test eine Rolle
spielen etc.).
Im Folgenden eine simple Umsetzung der Idee für das klassische
unittest-Modul:
Anzumerken ist hierbei, dass bei umfangreichen Testsuites _cleanup
noch erweitert werden muss. Beispielsweise muss man die ABC-Registry
aufräumen, Caches (re, struct, urllib, linecache) und warnings müssen
aufgeräumt werden, etc.
Jetzt muss man nur noch unittest beibringen, dass es die obige
TestSuite benutzen soll. Benutzt man keinen besonderen Test-Runner,
kann man das beispielsweise einfach wie folgt tun:
Führt man die Tests jetzt aus, führt das zu folgender Ausgabe:
.....<__main__.SpamTest testMethod=test_observable> leaks: [40, 40]
----------------------------------------------------------------------
Ran 5 tests in 0.007s
Jetzt weiß man zumindest, dass der Test test_observable leckt. Was
genau leckt, muss man aber immer noch selbst herausfinden. Was nicht
unbedingt immer leicht und offensichtlich ist.
In Python 3 gibt es ein neues Statement nonlocal, das es erlaubt,
bei verschachtelten Funktionen in der inneren Funktion einen neuen
Wert an einen lokalen Namen der äußeren Funktion zu binden. In Python
2 war es nur möglich, sich auf einen lokalen Namen der äußeren
Funktion zu beziehen.
Note
Der größte Teil dieses Blogeintrags dürften
Implementierungsdetails von CPython sein und müssen keinesfall für
andere Python-Implementierungen gelten.
Dazu schauen wir uns als erstes eine Funktion näher an:
deff():local_name=42
(In Python implementierte) Funktionien haben in CPython ein
sogenanntes Code-Objekt, das als func_code-Attribut (in Python 3
als __code__-Attribute) an die Funktion gebunden ist. In ihm sind
der Bytecode, alle verwendeten lokalen Namen und Konstanten und andere
Informationen gespeichert. All das ist direkt in Python selbst
benutzbar und sollte durchaus einmal im interaktiven Interpreter
ausprobiert werden. Der generierte Bytecode für diese Funktion sieht
wie folgt aus (die Ausgabe wurde mit dem dis-Modul von Python
gemacht):
Dabei ist die 2 in der ersten Spalte die Zeilennummer, die Spalte
rechts davon ist der Offset des Opcodes im Bytecode, gefolgt vom
Opcode selbst und (falls vorhanden) einem Argument. In Klammern steht
dann jeweils, was das Argument bedeutet.
Im konkreten Fall für die Funktion f wird also zunächst die
Konstante 1 geladen, in unserem Fall ist das 42. 42 ist hierbei im
co_consts-Attribut des Code-Objekts gespeichert. Diese 42 wird dann
auf einem internen Stack der CPython-VM abgelegt. Der nächste Opcode,
STORE_FAST, speichert den obersten Wert vom Stack in die lokale
Variable local_name. Lokale Variablen werden dabei einfach als Array
im aktuellen Frame umgesetzt (wobei dieses Array nicht von Python aus
erreichbar ist). Der Opcode speichert also einfach den Wert als ersten
Eintrag im Array. Falls jemand auf den lokalen Namensraum als Dict
zugreifen wird (etwa über locals() oder das f_locals-Attribut
eines Frames), wird das Dict des lokalen Namenraumes dann einfach mit
den Werten aus dem Array aktualisiert. Die benötigten Informationen,
nämlich die Namen der lokalen Variablen, finden sich im
co_varnames-Attribut des Code-Objekts.
Als nächstes wird dann die Konstante None geladen und zurückgegeben
(da eine Funktion in Python ja implizit None zurückgibt, wenn kein
anderer Rückgabewert angebeben wurde).
Als nächstes betrachten wir eine Funktion, die eine geschachtelte
Funktion zurückgibt, die einen Namen der äußeren Funktion benutzt:
Was hat sich geändert? Zunächst einmal fällt auf, dass in outer()
nicht mehr STORE_FAST zum Speichern der lokalen Variable benutzt
wird, sondern STORE_DEREF. Das liegt daran, dass outer_name von
der inneren Funktion benutzt wird. Anstatt in einem Array wird der
Wert jetzt in einem sogenannten Cell-Objekt gespeicher (wobei diese
Cell-Objekte wiederum auch in einem Array im Frame gespeichert
werden). outer_name ist auch nicht mehr in co_varnames
aufgelistet, sondern in co_cellvars. Als nächstes wird dieses
Cell-Objekt dann auf den Stack geladen (mit LOAD_CLOSURE) und in ein
Tupel gepackt (BUILD_TUPLE). Dieses Tupel bildet dann das
func_closure-Attribut der neuen Funktion inner(), die mit
MAKE_CLOSURE erstellt wird. Schließlich wird die neu erstellte
Funktion, die sich oben auf dem Stack befindet, mit STORE_FAST an
den lokalen Namen inner gebunden, wieder geladen und zurückgegeben.
In inner() wird einfach der Wert aus dem Cell-Objekt geladen, das
aus func_closure genommen wird und zurückgegeben. Genau genommen
werden die Cell-Objekte aus func_closure zum Array der Cell-Objekte
im Frame hinzugefügt, wenn das Frame erstellt wird.
Was passiert jetzt, wenn man in inner()outer_name einen Wert
zuweist?
Wie man sieht, wird outer_name in inner() automatisch zu einer
lokalen Variable, der Wert in outer() selbst wird nicht
geändert. Versucht man vorher außerdem auf outer_name zuzugreifen,
erhält man einen UnboundLocalError.
Für diesen Fall bietet Python 3 das neue nonlocal-Statement. Damit
kann man in inner() sagen, dass man den Wert in outer() ändern
mag:
Wie man sieht, wird zum Laden der Variable wieder LOAD_DEREF
benutzt. Was jetzt aber interessant ist: Zum Speichern wird
STORE_DEREF benutzt, also derselbe Opcode, der auch in outer()
benutzt wird. Was ja auch eigentlich logisch ist, denn in beiden
Fällen wird der Wert in ein Cell-Objekt geschrieben -- sogar in
dasselbe Cell-Objekt. Das bringt uns also zur folgenden Erkenntnis:
Das nonlocal-Statement wäre auch problemlos in Python 2.x möglich,
es fehlt nur die Syntax dafür (und natürlich auch die
Compiler-Unterstützung).
Also werden wir im Folgenden probieren, das nonlocal-Statement in
Python 2.x umzusetzen. Dazu müssen in der inneren Funktion alle
STORE_FAST-Opcodes für einen nonlocal-Namen durch STORE_DEREF
ersetzt werden und alle LOAD_FAST durch LOAD_DEREF. Außerdem
müssen die Namen dann zu co_freevars hinzugefügt werden. Man kann
ein Code-Objekt jedoch nicht einfach so verändern, sondern muss ein
neues erstellen. Da man Funktionen verändert, drängt sich ein
Dekorator geradezu auf:
Dabei zeigt sich ein weiteres Problem: Man muss an die Cell-Objekte
der äußeren Funktion bekommen. Dies tun wir, indem wir nach jedem
STORE_DEREF (also jedes mal, wenn ein Wert in ein Cell-Objekt
geschrieben wird), zwei weitere Opcodes einfügen: LOAD_CLOSURE und
STORE_FAST. Damit wird direkt nach dem Speichern das Cell-Objekt auf
den Stack geladen und in einem lokalen Namen gespeichert. Dazu führen
wir für jeden Namen, der ein Cell-Objekt hat, eine lokale Variable
_[<Name>] ein. Die [] deshalb, dass der Name nicht mit einem
anderen lokalen Namen zu Konflikten führt. Im Dekorator der inneren
Funktion kann man dann einfach über f_locals vom Frame des
Aufrufers auf die Cell-Objekte zugreifen, den man mit
sys_getframe(1) bekommt. Der Dekorator für die äußere Funktion
sieht also so aus:
Dabei ist anzumerken, dass dabei nicht alle Fälle abgedeckt sind und
außerdem davon ausgegangen wird, dass die äußere Funktion bereits
Cell-Objekte für die nonlocal-Namen benutzt (in diesem Fall wird dies
ausgelöst durch die getter()-Funktion).
Außerdem wird Code-Klasse von Aaron Gallagher benutzt, die bereits
aus einem vorigen Blogeintrag bekannt ist:
Bevor man stundenlang nach scheinbar obskuren Fehlern sucht: Einfach
einmal schauen, ob die Software detailiertere Logging-Optionen kennt
und dann den Log aufmerksam lesen.