Bug Reports

Heute habe ich mal wieder einen guten Artikel zum Thema Bug-Reports gefunden. Der Artikel von ESR, How to ask questions the smart way, sollte ja hinlänglich bekannt und referenziert sein. Aber how to report bugs effectively liest sich gerade aus der Sicht eines Programmierers ganz gut, und ist vor allem um einiges kürzer. :) Insbesondere die Zusammenfassung ist kurz, prägnant und lesenswert für jeden Bug-Melder.

Es gibt auch eine deutsche Übersetzung.

Berlios kills projects

Two days ago, the Berlios Developer site lost one of my projects: Cython. I have no idea where it went, it just disappeared silently. The last mail went through the mailing list at 18:42, 2008/01/16, and since then, the list is dead and the project is no longer available from the developer site. I've been trying to contact any of the admins through either the problem tracker or the support form on the Berlios site, but didn't get a response for two days so far. Personally, I find that a pretty inacceptable delay, but the back list in the problem tracker actually speaks for itself: the first open issue dates back to mid 2005! And Cython doesn't seem to be the first project to have disappeared. I guess we're just lucky we weren't using their Subversion...

Looks like that was the last project I'll ever host there...

XPath 2.0, XSLT 2.0 and Python

I noticed a few people getting desperate about XPath/XSLT 2.0 support in Python, especially since the normal sources like XML-SIG or comp.lang.python magically failed to provide helpful answers in the past.

A good place to look is the web site of the W3C XQuery group, which has a general and quite comprehensive list of XQuery 1.0 implementations. By design, these also implement XPath 2.0. Now, some of them provide their XPath2 support separately, some additionally implement XSLT2, and some even seem to have Python bindings. So there should at least be something useful to start from.

Another take on this: while some implementations are in C (just as the CPython interpreter) or at least C++, many others are written in Java. GNU's gcj is pretty good in compiling such tools and libraries into executable binaries and it also has support for generating the necessary C headers. What I'd love to see is one of the good XPath2/XQuery tools compiled as a Python extension and/or interfaced with lxml at the C level.

One project that did something like that was PyLucene. They now seem to have switched to a new tool called JCC, which (according to the docs) generates a complete CPython extension from a Java application that interfaces with the JVM through the JNI. While I like the simplicity of this (I haven't used it yet), I would still prefer a GCJ compiled binary over a dependency on a full-fledged JVM. But I definitely like both approaches. Any volunteers? :)

easy, easy, XML

I keep answering XML questions on Python's XML-SIG mailing list and on comp.lang.python. Most of them go: "Here's my SAX/minidom/PyXML code. I can't find the bug. Can you help me?"

Yes, I can. My default answers are:

  • Don't use SAX. It's well designed to hide the bugs in your code, but it's no good for doing XML work.
  • Don't use PyXML. It hasn't been maintained for years, so if you have problems with it, you're lucky if there is someone still alive who can help you out.
  • Avoid minidom and other DOM implementations. DOM is good if you need to write code the Java way. It's no good if you want to get XML work done.

The second paragraph of my answer then goes:

Use ElementTree or lxml instead. The first is part of the standard library since Python 2.5, and the latter is available from http://lxml.de/. Both are mostly compatible, both are well maintained, both provide mature and stable libraries that are easy to use. Read the tutorial and get started.

It's an advantage for Python to have these tools available, and I'm still missing a straight link from the Python homepage to both of them. These are the tools that newbees should run straight into when they look for XML support in Python, not the outdated pages that are currently hidden behind the "XML" link.

IT-Kompetenz für Auszubildende?

Gestern in den Heute-Nachrichten: Auszubildende haben mangelnde IT-Kompetenz. Ein Mitarbeiter der Druckbranche meint: "Bald sind alle Maschinen mit Touch-Screen ausgerüstet, um sie besser einstellen zu können." Da ist dann IT-Kompetenz gefragt, klar.

Ein Sprecher der deutschen Niederlassung der Firma Microsoft meint, die Auszubildenden bräuchten vor allem Kompetenz in den Bereichen Textverarbeitung und Tabellenkalkulation. Klar. Wie praktisch, dass seine Firma in diesen Bereichen zufällig gerade mit hochpreisigen Produkten den Markt beherrscht.

Leider mal wieder ein völlig uninformierter Beitrag zum Thema Bildung, Ausbildung und IT-Kompetenz. Dabei ist das doch gar nicht so schwer. Um eine Spezialanwendung bedienen zu können, helfen IT-Kenntnisse überhaupt nichts. Das wird ohnehin vor Ort gelernt. Mausschubsen und Menü-Klick-Know-How sind dabei nämlich nicht die höchste Hürde, insbesondere nicht bei der Bedienung per Touch-Screen. Und um "mit Computern umgehen" zu können, ist "IT-Kompetenz" sicherlich auch nicht nötig. Bei der aktuellen Benutzerfreundlichkeit von Systemen wie Kubuntu (oder KDE allgemein) oder aktuellen Web-Browsern wie Firefox und Opera sind auch Computerneulinge schnell gut aufgehoben.

Ein Problem ist da schon eher, dass Firmen sich viel zu oft auf die sogenannten "Standardprodukte" aus dem Hause Microsoft verlassen, die durch ihre seit Jahren unveränderten Fehler und Inkompatibilitäten das Arbeiten unnötig erschweren. Hier ist der eigentliche Ort, an dem mehr IT-Kompetenz gefragt wäre.

Das immerhin am Rande des Berichts angeschnittene Problem, dass Auszubildende auch zu wenig Erfahrung mit dem Internet besitzen (klarer Fall: "fehlende Internet-Kompetenz"), lässt sich da schon eher als Problem erkennen. Immer mehr Stellenausschreibungen werden nur noch über die Web-Seite des Betriebes publiziert. Eine gute Online-Bewerbung gilt in manchen Branchen durchaus als Pluspunkt, aber in jedem Fall macht es der Online-Auftritt des zukünftigen Arbeitgebers den Bewerbern wesentlich einfacher, ihre Bewerbung zielgenau auf das Unternehmen zuzuschneiden - in jedem Fall ein klarer Vorteil. Und auch dem zukünftigen eigenen Internetaustritt der selbst gegründeten Firma ist etwas Medienkompetenz und Erfahrung mit Web-Seiten und der Kommunikation über Internet-Dienste sicherlich nicht abträglich, selbst wenn die eigentliche Erstellung des Web-Auftritts in Auftragsarbeit erfolgt.

Das wirkliche Problem ist also nicht mangelnde "IT-Kompetenz", sondern Defizite im Umgang mit Medien. Das Herausfiltern von relevanten ("echten") Informationen aus mehr oder weniger umfangreichen Berichten, Texten und Web-Auftritten, sowie das Präsentieren von Informationen in gut verständlicher oder vielleicht auch mal tendenziöser Form. Themen, die seit etlichen Jahren unverändert die Wunschanforderungen an die Lehrpläne dominieren und die auch Einzug in zahlreiche Bildungsstudien gefunden haben (Stichwort "Pisa"). Aber ein Schrei nach mehr "IT-Kompetenz" klingt natürlich gleich viel wichtiger und drängender, klar. Schade nur, dass große Worte leicht mal die wirklich großen Probleme in den Hintergrund treten lassen.

The Cython Project

I'm happy to assist at the birth of the Cython project, an advanced fork of the Pyrex code generator.

Why was Cython born? - First of all, because Pyrex is great. It was designed and implemented by Greg Ewing and allows you to write C extensions for the Python interpreter more or less in Python and to have them automatically translated into C code - including all the tedious ref-counting and other beauties of the Python C-API. lxml is a great example for what it can achieve, and a great example for wrapping external C libraries with Pyrex/Cython.

However, over the years, it has shown that the original author has become a bottleneck in the further development of Pyrex. A lot of contributed patches never made it into the official distribution, and several people started branching off their own enhanced versions of Pyrex. The Cython project aims to integrate these patches back into an official distribution, and to further enhance the Cython compiler in a community effort.

Cython already supersedes Pyrex in a couple of ways. It works on 64 bit systems with Python 2.5 (Py_ssize_t). It supports list comprehensions and the conditional statements of Python 2.5. It knows about several Python types and can generate optimized code for them. It can generate glue code for a C level API between extension modules (a feature that lxml makes heavy use of already).

And: it is an open project with an open bug tracker and an open, distributed version control system (mercurial). And the new maintainers are happy to receive contributions, and to integrate them into a mainstream distribution.

So, in case you ever wondered if Pyrex couldn't do a little more of what you need - go and give Cython a try. It may already be what you need, but it is most likely close to what you want. And any help to get it even closer is warmly appreciated.

XML mit Python: lxml.objectify

lxml hat sich inzwischen zu einer der besten und schnellsten XML Bibliotheken unter Python gemausert. Gerade in Bezug auf Benutzerkomfort ist lxml jedoch nur schwer zu übertreffen. Neben der ElementTree kompatiblen lxml.etree API gibt es inzwischen auch noch eine weitere XML API: lxml.objectify.

lxml.objectify ist ein Werkzeug zur XML-Verarbeitung, das bestmöglich der Schnittstelle von Python-Objekten nachempfunden ist. Soll heißen: Mit lxml.objectify fühlt sich XML an wie Python. Ein einfaches Beispiel:

  >>> import lxml.etree as et
  >>> from lxml import objectify

  >>> item = objectify.Element("item")
  >>> item.title = "Best of Schmidteinander XIV"
  >>> item.price = 17.98
  >>> item.price.set("currency", "EUR")

  >>> order = objectify.Element("order")
  >>> order.customer = "Herbert Feuerstein"

  >>> order.append(item)
  >>> order.item.quantity = 3

  >>> order.price = sum(item.price * item.quantity
                                for item in order.item)

  >>> print lxml.etree.tostring(order, pretty_print=True)
  <order>
    <customer>Herbert Feuerstein</customer>
    <item>
      <title>Best of Schmidteinander XIV</title>
      <price currency="EUR">17.98</price>
      <quantity>3</quantity>
    </item>
    <price>53.94</price>
  </order>

Auffällig ist hier sicherlich, dass lxml.objectify mit allerlei Datentypen umzugehen weiß, und dass sich XML-Elemente auch wie normale Python Datentypen verhalten. Strings und Zahlen fügen sich nahtlos in sie XML-Struktur ein und natürlich ist auch die Unterstützung anderer Datentypen vorgesehen.

Noch ein bisschen einfacher wird es bald mit der E-Factory gehen, die Fredrik Lundh entwickelt hat, und die mit lxml 1.3 Einzug in lxml.etree gehalten hat. In lxml.objectify wird sie ab der nächsten Version unterstützt. Damit wird die Erzeugung von (Sub-)Elementen noch ein wenig schöner und weniger fehleranfällig:

  >>> ITEM = objectify.E.item
  >>> ORDER = objectify.E.order

  >>> item = ITEM()
  >>> item.title = "Best of Schmidteinander XIV"
  >>> order = ORDER()
  >>> order.customer = "Herbert Feuerstein"
  >>> order.append(item)

Dass lxml.objectify Strukturelemente nicht automatisch erzeugt ist durchaus gewollt. Durch die E-Factory ist dies allerdings auch kein Aufwand mehr. Ganz im Gegenteil, es wird sichtbar, wo Elemente erzeugt werden und es werden nur solche Elemente erzeugt, die in der XML-Sprache auch vorgesehen sind. So lässt sich ganz einfach vorab ein entsprechendes Element-Vokabular definieren, dass dann im Programmcode sicher verwendet werden kann.

XML mit Python: lxml

Unter den XML Bibliotheken für die Programmiersprache Python ist lxml.etree ein wahres Goldstück. Es schafft den schwierigen Spagat, die extrem schnellen XML-Bibliotheken libxml2 und libxslt hinter einer pythonesquen und spielend leicht zu verwendenden Schnittstelle zu verstecken: der ElementTree API, die es um so praktische und effiziente XML-Werkzeuge wie XPath oder XSLT erweitert. lxml ist leicht in Python zu erweitern und sogar so flexibel, dass es die Implementierung beliebiger XML-Schnittstellen erlaubt.

Das lxml Projekt bemüht sich, eine portable Python-Erweiterung anzubieten, die einfach mit easy-install zu installieren ist. Unter den meisten Betriebsystemen funktioniert das problemlos mit einem

    easy_install lxml

Da eine Installation von Hand unter Windows wie üblich etwas komplizierter ist, stehen dafür fertig kompilierte Binaries zur Verfügung. Auf anderen Plattformen kümmert sich EasyInstall automatisch um das Kompilieren und Installieren. Danach reicht ein Import und lxml.etree steht zur Verfügung:

    >>> from lxml import etree

Die wichtigste API Klasse heißt einfach Element. Es ist eine Container-Klasse, die sich wie verschiedene aus Python bekannte Standardklassen verhält. Um ein Element anzulegen, reicht ein Aufruf der entsprechenden Factory-Funktion:

    >>> wurzel = etree.Element("wurzel")

Dadurch wird ein XML Element mit dem Tag-Namen "wurzel" erzeugt. Um den Tag-Namen abzufragen gibt es das Attribut tag:

    >>> print wurzel.tag
    wurzel

XML ist eine Baumstruktur, und so sind auch Elemente in Bäumen organisiert. Um dem Wurzelelement ein Kindelement hinzuzufügen, kann die append() Methode verwendet werden:

    >>> wurzel.append( etree.Element("kind1") )

Da diese Operation extrem häufig verwendet wird, gibt es eine weitere Factory-Funktion SubElement(), die dies einfach und effizient erledigt. Sie wird genauso verwendet wie die Element() Factory, erhält jedoch zusätzlich ein Elternelement als ersten Parameter:

    >>> kind2 = etree.SubElement(wurzel, "kind2")
    >>> kind3 = etree.SubElement(wurzel, "kind3")

Um dann schließlich noch sehen zu können, dass das Ganze auch wirklich XML ist, lässt es sich über die tostring() Funktion serialisieren:

    >>> print etree.tostring(wurzel, pretty_print=True)
    <wurzel>
      <kind1/>
      <kind2/>
      <kind3/>
    </wurzel>

Schon diese kleinen Beispiele zeigen, wie einfach die Erstellung von XML Dokumenten mit lxml.etree ist. Weiter Beispiele finden sich im (englischsprachigen) lxml.etree Tutorial, von dem ich hier von Zeit zu Zeit ein wenig wiedergeben werde.

Deutschland einig Raucherland

Warum nur funktioniert der Raucherschutz in diesem Land so gut? Müssen Nichtraucher denn wirklich in irgendwelche Ecken abgedrängt werden? Warum kann ich mir ein Restaurant nicht danach aussuchen, was es dort zu essen gibt, oder wie es drinnen aussieht, oder mit wem ich dort hin gehen möchte? Warum muss ich immer erst rückwärts wieder herausfallen, um zu merken, dass ich die dicke Luft darin heute abend doch nicht mehr mit der Machete zertrennen möchte?

Natürlich kann ich Stammgast im Café Ignaz in München werden, dem wahrscheinlich ohnehin besten Restaurant/Café/Konditor in der Stadt. Aber vielleicht möchte ich ja auch in anderen Städten hin und wieder mal ausgehen. Oder auch einfach mal zum Italiener oder zum Türken um die Ecke gehen. Zumindest in Italien gibt es inzwischen Gesetze, die mir das erlauben würden. Aber der Italiener in Italien ist eben nicht gerade um die Ecke.

Was mache ich zum Beispiel, wenn mal ein Geschäftsessen ansteht? Natürlich kann ich vorher quer durch die Stadt telefonieren und irgendwie versuchen, ein halbwegs akzeptables Restaurant zu finden. Um dann zu merken, dass entweder ich oder mein Gegenüber mit der Küchenwahl nichts anfangen kann. Und der Koch des 20qm Lokals auch nicht gerade auf die Schnelle mal das Menü umstellen kann.

Ich habe das Gefühl, Restaurants in diesem Land haben gar keinen Anreiz zum Nachdenken, weil sie sich sowieso alles erlauben können. So sind beispielsweise in den letzten Jahren die Preise in ausgewählten Lokalitäten dermaßen exorbitant gestiegen - und die Küche musste trotzdem nicht schließen. Stecken die wirklich so viel Geld in Marketing? Neulich war ich in Bonn im Opera, einem türkischen Restaurant nahe der Oper. Dort gibt es einen gemischten Vorspeisenteller ab zwei Personen, der mit 8 Euro zu Buche schlägt. Pro Person, versteht sich. Und weil die türkische Spezialitätenküche ja für ihre guten Vorspeisen bekannt ist, schlägt mensch dort natürlich auch gerne zu. Und muss sich doch etwas wundern, wenn dann ein schmales, längliches Tellerchen zentral auf dem Tisch plaziert wird, mit einer Messerspitze einzeln abgezählter Eisbergsalathäcksel, fünf Champignons, zwei geschnittene Auberginenscheiben mit Soße und noch ein paar einzeln abgezählten Leberbällchen. Nicht gerade die bekanntesten Spezialitäten der türkischen Küche - aber auch nicht gerade das, was mensch so für 16 Euro erwartet. Klar, dass es dafür am Ende auch noch ordentlich Trinkgeld gibt.

Muss ich erwähnen, dass dieses Lokal natürlich über eine Nichtraucherecke verfügt? In der sich einem schon vom Nasenöffnen der Magen verdreht, weil die kaum merkliche Lüftung sich mit Kraft daran macht, aus dem offenen Raucherbereich nebenan den Giftmüll im ganzen Raum gleichmäßig zu verteilen.

An solchen Abenden fällt mir eigentlich nichts mehr ein. Wie viele Monate kann ein Mensch ohne Nahrung überleben? Und wie viele Minuten ohne Luft? Sollte da nicht irgendwo mal eine Priorisierung stattfinden?

Ach ja, natürlich. Die findet ja statt. Neulich im Insel-Hotel in Bad Godesberg. "Ich bin heute Nacht aufgewacht, weil in meinem Nichtraucherzimmer der Rauch in Schwaden unter der Tür durchgezogen ist." - "Oh, das tut uns natürlich leid. Von hier unten aus der Bar kam es ganz bestimmt nicht, das zieht ja nicht bis da oben rauf." - "Haben Sie schonmal darüber nachgedacht, das Hotel insgesamt rauchfrei zu machen, nicht nur einzelne, handverlesene Zimmer?" - "Na, ich bin ja selbst Nichtraucher. Aber das würden unsere langjährigen, rauchenden Kunden nicht mitmachen. Sie hätten hören sollen, wie die sich beschwert haben, als wir den Frühstücksraum rauchfrei gemacht haben." Moment, der Frühstücksraum? Der war nicht immer rauchfrei? Und was heißt eigentlich rauchfrei, wenn direkt daneben der Bar-Bereich ist, von dem aus noch der Gestank des letzten Abends herüber zieht? "Wir fänden es ja auch besser, wenn die Regierung das durchsetzen würde." - "Was ist eigentlich mit der freiwilligen Selbstverpflichtung des Gastronomieverbandes?" - "Ja, die wollten das ja auch machen." Aha. 'Die'. Klar. Na ja, die Priorisierung hatte ich dann doch verstanden.

Ist es denn so falsch, zu meinen, dass Tabakrauch in der Lunge von Rauchern wesentlich besser aufgehoben ist als in der von Nichtrauchern? Sollte es da nicht so etwas geben wie ein Recht auf Selbstbestimmung? Ich meine, eines, wo ich auch ernsthaft entscheiden kann, weil ich die Wahl habe. Und mit Wahl meine ich nicht: "gehe ich essen oder nicht", sondern schon so etwas wie: "wo würde ich heute mal gerne hingehen?".

Da müssen sich noch viele Prioritäten verschieben, bis daraus etwas wird...

Finally some I/O benchmarks on lxml

In case you don't know, lxml is one of the most feature-rich XML libraries for Python. It closely follows the ElementTree API and extends it with support for XSLT, XPath, RelaxNG and loads of other XML candies, all driven by the marvelous libxml2/libxslt libraries. I'm one of the authors of lxml, so be careful, I may be biased.

The benchmarks I ran during the development of version 0.9 were mainly geared towards comparisons of the API. Some of them were even chosen explicitly to show where lxml's performance is low enough to merit some work. Now, I finally came up with some simple benchmarks on the I/O part. And those put lxml into a completely different light.

Imagine you wanted to serialize a large XML tree to UTF-8 (which is the internal encoding used in lxml and arguably the most common serialization for XML):


lxe: tostring_utf8             (U- T1     )   21.6062 msec/pass

ET : tostring_utf8             (U- T1     )  658.4980 msec/pass

cET: tostring_utf8             (U- T1     )  618.3270 msec/pass

or to UTF-16 (which is not the internal encoding):


lxe: tostring_utf16            (S- T1     )   24.6755 msec/pass

ET : tostring_utf16            (S- T1     )  668.2270 msec/pass

cET: tostring_utf16            (S- T1     )  629.3236 msec/pass

And how about serializing a tree to UTF-8, write it into a StringIO object and then parsing it back into an element tree:


lxe: write_utf8_parse_stringIO (S- T1     )  188.3298 msec/pass

ET : write_utf8_parse_stringIO (S- T1     ) 1143.8117 msec/pass

cET: write_utf8_parse_stringIO (S- T1     )  810.7611 msec/pass

or with an intermediate step of unicode() conversion after serializing it to a UTF-8 string, and before we pass it back into XML():


lxe: tostring_utf8_unicode_XML (U- T2     )  209.5674 msec/pass

ET : tostring_utf8_unicode_XML (U- T2     ) 1022.0318 msec/pass

cET: tostring_utf8_unicode_XML (U- T2     )  678.8596 msec/pass

Interesting, isn't it? The reason for this is that lxml's parser (libxml2) runs completely in C and does not instantiate any Python representations of elements. What this tells us is that lxml is extremely fast on I/O, much faster than (c)ElementTree, as long as you only touch a few elements in the tree. But that's the common case, right? You parse an XML file, touch a few nodes in it, maybe run some XPaths or XSLTs on them, change the ordering here, remove some nodes there and then serialize it. Well, that's what lxml excels in!

I should mention that I also wrote the benchmark script, but I did not write the numbers above myself! :-) The complete benchmark results are also available.

Any new users attracted?