Mittwoch, 29. Juni 2011

Thomas Heller tritt als ctypes-Maintainer zurück

Das Python-Entwicklungs-Team schuldet dem ctypes-Maintainer Thomas Heller ein großes Dankeschön. Anfang des Monats verkündete Thomas sein Ausscheiden aus dem CPython-Projekt, das seit Python 2.5 die Heimat seiner ctypes-Bibliothek ist.

Ich hatte die Chance mit Thomas zu reden und er erzählte mir von seiner Geschichte mit Python und seinen Projekten ctypes und py2exe.

Python

Auf der Suche nach Ressourcen um Python zu lernen, fiel Thomas 1999 Mark Lutz' Programming Python in die Hände und er wurde direkt von der Sprache fasziniert. Er war gerade dabei Scheme als Erweiterungssprache für ein großes, für Windows geschriebenes, C-Programm zu ersetzen.

Sein erster Beitrag zu CPython (und Open Source im Allgemeinen) war ein kleiner Windows-bezogener Patch zu distutils. Sein Interesse an distutils brachte ihn schließlich zur Erstellung des bdist_wininst-Kommandos, das "point-and-click"-Windows-Installer erstellt. Danach wurde er von Greg Ward in die python-dev Gruppe eingeladen, wo er schließlich Commit-Rechte bekam.

py2exe

Wie viele Windows-Nutzer hatte er das Bedürfnis Python-Anwendungen als einzelne, ausführbare Datei weiterzugeben. Frühe Lösungsansätze für das Problem kamen von Fredrik Lundhs squeeze und Christian Tismers sqfreeze und Thomas hat mit mehreren Patches zu Gordon McMillans Installer-Projekt beigetragen.

Sein Interesse an distutils führte Thomas dazu Installer als eine Erweiterung der Bibliothek zu portieren. Jedoch endete es damit, dass er den Code umschrieb, um sicherzustellen, dass die existierenden Möglichkeiten von distutils genutzt werden. Am Ende wählte er den einfachen, aber beschreibenden Namen py2exe für das Projekt.

ctypes

Die Idee zu ctypes kam aus dem Bedürfnis nach mehr als was pywin32 seinerzeit bot. Zusätzlich benötigte er für seine Arbeit mit Scheme ein Interface zur Windows API, wie er es auch für seine Arbeit mit Python brauchte, also betrieb er sein Projekt weiter.

ctypes hatte sein erstes öffentliches Release in 2003, etwa zur Zeit des Python 2.3-Release, nachdem Thomas zahlreiche Anfragen bekam das Projekt zu veröffentlichen. Er erwähnte sein ehemals kleines, privates Projekt auf seiner Starship-Seite, aber in kurzer Zeit wurde es zu einer weit verbreiteten Bibliothek.

Ursprünglich startete er das Projekt unter Windows, aber schnell wurde der Ruf nach einer Linux-Portierung laut, die er mit Hilfe der Community umsetzte. Mit der Linux-Portierung wurde libffi im Projekt eingeführt, das er auch unter Windows zu benutzen begann, um die zugrundeliegende Implementierung auszutauschen.

2006 markierte ein 1.0-Release für ctypes, das sich mit der Aufnahme in die Standardbibliothek von Python 2.5 deckte. Nach Jahren harter Arbeit und vielen Veröffentlichungen pro Jahr, war ctypes nun mit Python gebündelt und standardmäßig für eine viel breitere Zielgruppe zugänglich.

Es waren viele Leute nötig, um ctypes dorthin zu bringen, wo es heute ist, und Thomas will jedem danken, der mitgewirkt hat, aber vor allem Robin Becker. Robin war in den frühen Phasen des Projekts beteiligt und steuerte sowohl Wissen, als auch Unterstützung bei.

Ein neuer ctypes-Maintainer

Nach all der harten Arbeit, die Thomas über die Jahre investiert hat, würde er ungern sehen, dass das Projekt zum Stillstand kommt. Wenn Du Erfahrung mit C und Zeit dem Python-Projekt zu helfen, würde die Python-Community Deine Dienste sehr schätzen. Mehr Informationen gibt es im neuen Developer Guide und im Bug Tracker.

Englische Version

Samstag, 25. Juni 2011

Meet the Team: Benjamin Peterson

Dieser Post ist Teil der "Meet the Team" Serie, die kurz das Python-Kernentwickler-Team vorstellen soll.

Name:Benjamin Peterson
Standort:Minnesota, USA
Homepage:http://benjamin-peterson.org
Blog:http://pybites.blogspot.com

Wie lange nutzt du schon Python?

3 1/2 Jahre.

Wie lange trägst du schon zum Kern bei?

Diesen 25. März genau drei Jahre.

Wie wurdest du Kernentwickler? Erinnerst du dich an deinen ersten Beitrag?

Mein erster Vorschlag wurde von Guido selbst zurückgewiesen. Zum Glück blieb ich hartnäckig und es wurden ein paar Patches akzeptiert. Ich glaube mein erster Beitrag war eine Neuordnung der Misc/ACKS-Datei.

An welchen Teilen von Python arbeitest du jetzt?

Ich mag den Parser-, Compiler- und Interpreter-Kern, aber ich bin bekannt dafür in fast jedem Teil der Python-Kernentwicklung herumzupfuschen... außer Windows!

Was machst du mit Python wenn du nicht gerade am Python-Kern schraubst?

Ich benutze es um einen Python-Interpreter zu bauen (http://pypy.org)! Ich bin im Innersten wirklich ein Python-Implementierer. :) Ich bin der Schöpfer von six, einer Python 2 und 3 Kompatibilitätsbibliothek.

Was macht du wenn du nicht gerade programmierst?

Musik komponieren, Klarinette spielen und Mathebücher lesen. Hin und wieder wandere ich auch ein bisschen.

Englische Version

Donnerstag, 23. Juni 2011

Deprecations zwischen Python 2.7 und 3.x

Eine kürzliche Diskussion auf python-dev zeigte ein Problem mit Pythons momentaner Verfahrensweise bezüglich Deprecations auf, das Entwickler betrifft, die von Python 2.7 auf aktuelle Versionen von Python 3.x umsteigen. Aufgrund dieses Problems hat das Entwickler-Team die aktuelle Deprecation-Policy angepasst, um die Tatsache zu berücksichtigen, dass Python-Nutzer normalerweise direkt von Python 2.7 auf die letzte Version von 3.x umsteigen, ohne je ältere Versionen gesehen zu haben.

Hintergrund

Python hat ein starkes Bekenntnis zur Rückwärtskompatibilität. Keine Veränderung ist erlaubt, ohne dass sie den Kompatibilitätsrichtlinien entspricht, was im Grunde heißt, dass korrekte Programme auch unter neuen Python-Versionen korrekt laufen. Aber das ist nicht immer möglich, beispielsweise wenn eine API klar kaputt ist und durch etwas anderes ersetzt werden muss. In dem Fall verfolgt Python eine Deprecation-Policy, die besagt, dass zu entfernende Features in einem einjährigen Übergangszeitraum formell deprecated werden. In diesem Übergangszeitraum muss eine DeprecationWarning ausgegeben werden, um den Entwicklern Zeit zu geben ihren Code zu aktualisieren. PEP 5 beschreibt die vollen Details von Pythons Deprecation-Policy. Da Veränderungen nur in neuen Python-Releases gemacht werden und es normalerweise eine 18-monatige Lücke zwischen Releases gibt, bedeutet das, dass dieser Zeitraum normalerweise einem Release entspricht.

Die eine Ausnahme zu dieser Verfahrensweise war Python 3. Der große Versionssprung zwischen Python 2 und Python 3 war genau dazu gedacht Veränderungen zu machen, die die Rückwärtskompatibilität brechen, um den Python-Entwicklern die Möglichkeit zu geben Probleme zu beheben, die einfach nicht innerhalb der Verfahrensweise behoben werden konnten. Zum Beispiel Strings standardmäßig zu Unicode zu machen und Iteratoren statt Listen zurückzugeben.

Parallele Entwicklungszweige

Wohlwissend, dass der Übergang nach Python 3 Zeit brauchen würde, nach vielen Schätzungen 5 Jahre, gab es in einigem Umfang eine parallele Entwicklung von Python 2 und 3.

Da Python 2.7 der letzte Release von Python 2 sein wird, wurde vereinbart, dass hier der Wartungszeitraum erheblich ausgedehnt wird. Letztlich müssen Entwickler, die zu einer neueren Version von Python wechseln wollen, also den Sprung nach Python 3 machen.

Und genau hier liegt das Problem ...

Überraschungsdeprecations

In einem Thread auf python-dev, wurde darauf hingewiesen, dass eine spezifische Funktion der C-API, PyCObject_AsVoidPtr, entfernt wurde, ohne dass davor ausreichend gewarnt wurde. Und doch soll die Deprecation-Policy genau das verhindern! Was ist also passiert?

Die Änderung war Teil einer größeren Migration von einer älteren API (PyCObject) zu einer neueren und verbesserten API (PyCapsule). Das Problem ist, dass PyCObject die Standard-API war und sogar die einzige in Python 2.6 verfügbare. Das API wurde in Python 2.7 deprecated. In Python 3.2 gibt es sie nicht und PyCapsule sollte genutzt werden. Damit gibt es einen Übergangszeitraum vom Release von Python 2.7 (Juli 2010) bis zum Release von Python 3.2 (Februar 2011), also knapp 7 Monate. Das ist erheblich weniger als der 12-Monate-Mindestzeitraum und macht es Entwicklern schwer eine sinnvolle Auswahl von Python-Releases zu unterstützen.

Für jemandem, der von 3.0 zu 3.1 und danach zu 3.2 wechselt, ist der Übergang in Ordnung. Python 3.1 wurde im März 2010 mit der Deprecation veröffentlicht und so gab es in der Python 3.x-Serie einen Übergangszeitraum von fast 12 Monaten. Allerdings ist das nicht, was Entwickler normalerweise tun: Sie gehen von Python 2.7 direkt zur letzten Version von Python 3.x, in diesem Fall Python 3.2, und schaffen so das Problem. Dies war nie die Absicht von python-dev, aber beim Schreiben von PEP 5 wurde nicht an zwei parallele Versionen gedacht, die beide aktiv entwickelt wurden.

Was machen wir also?

Auch wenn der PyCObject/PyCapsule-API-Bruch ein echtes Problem ist, so ist es nicht unmöglich es zu umgehen, aber mindestens ein Poster hatte auf python-dev Schwierigkeiten damit. Insgesamt hätte das nie passieren dürfen.

Für den speziellen Fall von PyCObject/PyCapsule gibt es das Problem schon und es kann nicht viel dagegen getan werden. PyCObject wieder einzusetzen stand nicht wirklich zur Debatte, da es nur noch mehr Inkompatibilitäten schaffen würde. Jedoch war die allgemeine Ansicht, dass es möglich, wenn auch mühsam, wäre Code zu schreiben, der sich jeder verfügbaren API anpassen könnte. In der Tat war in Python 3.1 die PyCObject-API nur eine Schicht auf der PyCapsule-API. Es gab auch den Vorschlag, dass man falls nötig die Python 3.1 Implementierung extrahieren und als Dritt-Modul anbieten könnte. Man war sich einig, dass man ein "rückwirkendes" PEP schreiben würde, um die Gründe hinter der Veränderung zu beschreiben und Ressourcen zu dokumentieren, die Entwicklern beim Umstieg helfen können.

Allgemein gesprochen ist das Python-Entwicklungsteam nun mit dem Problem vertraut und wird daran arbeiten, dass es nicht wieder passieren wird. Guido veröffentlichte eine Überprüfung der Situation und schlug vor, dass man für den Moment in Python 3 sparsam mit Deprecations umgehen sollte. Mindestens sollten deprecated APIs erheblich länger behalten werden, bevor sie entfernt werden, um von Python 2.7 kommenden Entwicklern einen Migrationspfad zu bieten.

Indirekter wurde in dem Thread auch das Problem behandelt wie man effektiver Veränderungen in Python zeitnaher und einem breiterem Kreis kommunizieren könnte -- genau die Art von Problem also, für die dieses Blog geschaffen wurde.

Was bedeutet das alles?

In erster Linie bedeutet das, dass die Python-Entwickler nicht immer alles richtig machen. Niemand wollte den Entwicklern das Leben schwer machen, sondern es war einfach ein Problem, das nicht rechtzeitig entdeckt wurde.

Zweitens, dass das Beheben des Problems mehr Schaden als Nutzen kann, weshalb das PyCObject-API nicht wieder eingesetzt wurde. Zwar würde das Entwicklern helfen, die von der Veränderung gebissen wurden, aber zu dem Preis, dass Kompatibilitätsprobleme komplexer würden. In der Zwischenzeit müssen wir mit dem Problem leben und weiter machen. Lehren wurden daraus gezogen und wir werden den Fehler nicht nochmal machen.

Dies zeigt auch, dass das Python-Entwicklerteam von seinen Anwendern hören will. Kompatibilität ist wichtig und es wird alles daran gesetzt den Übergang zu neuen Versionen so schmerzlos wie möglich zu machen. Besonders Bibliotheken-Entwickler sollten mit einem angemessenen Maß an Aufwand mehrere Python-Versionen unterstützen können.

Schließlich heißt das: Entwickler haben Python 2.7 noch nicht verlassen. Obwohl es keine neue Features und kein Python 2.8 geben wird, sind die Ansichten der Python 2.7-Nutzer dennoch wichtig. Es ist wesentlich für die gesamte Python-Community, sicherzustellen, dass Nutzer zu 3.x wechseln können, wenn sie bereit sind.

Englische Version

Samstag, 4. Juni 2011

Von Polling, futures und Paralleler Ausführung

Eines der großen Problemfelder im modernen Computerwesen ist Stromsparen. Es hat besonders Gewicht in tragbaren Geräten (Laptops, Tablets, Handhelds). Eine moderne CPU ist in der Lage in viele stromsparende Modi zu wechseln, wenn sie nichts zu tun hat. Je länger sie untätig ist, desto tiefer der stromsparende Modus und desto weniger Energie wird verbraucht und damit hält der Akku eines Geräts umso länger mit einer einfachen Aufladung.

Stromspar-Modi haben einen Feind: Polling. Weckt ein Prozess die CPU periodisch auf, auch für so etwas triviales wie das Lesen einer Speicherstelle, um eventuelle Änderungen zu erkennen, verlässt die CPU den Stromspar-Modus, weckt alle seine internen Strukturen und wird erst wieder in den Stromspar-Modus wechseln, wenn der kleine periodischer Prozess seine beabsichtigte Arbeit schon lange beendet hat. Intel selbst ist besorgt.

Python 3.2 kommt mit einem neuen Standardmodul, um nebenläufige Arbeiten anzustoßen und auf ihre Beendigung zu warten: concurrent.futures. Während ich den Code durchsah, bemerkte ich, dass es in manchen seiner Arbeitsthreads und -prozesse Polling nutzte. "Manche", weil sich die Implementierung von ThreadPoolExecutor und von ProcessPoolExecutor unterscheidet. Erste pollt in jedem seiner Arbeitsthreads, während die Zweite es nur in einem einzigen Thread, dem "queue management thread", tut, der zur Kommunikation zwischen den Arbeitsprozessen genutzt wird.

Polling wurde hier nur für eine Sache genutzt: Um zu erkennen, wann die Abschaltprozedur ausgeführt werden soll. Für andere Aufgaben, wie das Einreihen von Callables oder das Holen von Ergebnissen, vorher eingereihter Callables, werden synchronisierte Queue-Objekte benutzt. Diese Queue-Objekte kommen entweder aus dem threading- oder dem multiprocessing-Modul, je nachdem welche Executor-Implementierung genutzt wird.

Also entwickelte ich eine einfache Lösung: Ich ersetzte das Polling mit einem Sentinel, dem eingebauten Sentinel None. Bekommt eine Queue ein None, dann wacht ein wartender Arbeiter natürlicherweise auf und überprüft, ob er sich Abschalten sollte oder nicht. Im ProcessPoolExecutor gibt es eine kleine Komplikation, da man neben dem einen "queue managing thread" N Arbeitsprozesse aufwecken muss.

Im ersten Patch gab es immernoch einen Polling-Timeout, wenn auch einen sehr großen (10 Minuten), sodass die Arbeiter an irgendeinem Zeitpunkt aufwachen würden. Der große Timeout existierte, für den Fall, dass der Code fehlerhaft ist und keine Benachrichtigung zum Abschalten durch den schon genannten Sentinel verteilt würde. Aus Neugier tauchte ich in den multiprocessing-Code und machte eine weitere interessante Entdeckung: Unter Windows benutzt multiprocessing.Queue.get() , mit einem von Null verschiedenen, nicht-unendlichen Timeout, ... Polling (weshalb ich Issue 11668 öffnete). Es benutzt eine interessante hochfrequente Art des Pollings, da es mit einem Millisekunden Timeout beginnt, der mit jedem Durchgang erhöht wird.

Natürlich macht die Benutzung eines Timeouts, egal wie groß, meinen Patch unter Windows zwecklos, da die Art der Implementierung ein Aufwachen jede Millisekunde bedeutet. Also hab ich die bittere Pille geschluckt und den riesigen Timeout entfernt. Der letzte Patch kommt komplett ohne Timeout aus und sollte damit, egal auf welcher Plattform, kein periodisches Aufwachen verursachen.

Historisch gesprochen, nutzte vor Python 3.2 jede Timeout-Einrichtung des threading-Moduls - und damit ein Großteil von multiprocessing, da multiprocessing selbst Arbeitsthreads für verschiedene Aufgaben nutzt - Polling. Dies wurde mit Issue 7316 behoben.

Englische Version