Interface für andere Sprache: Cython vs. ctypes

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Benutzeravatar
naeg
User
Beiträge: 33
Registriert: Dienstag 27. April 2010, 11:53

Hallo,

ich will für Python(2.x und 3) ein Interface für Prolog erstellen. Dies lässt sich am schönsten realisieren, in dem ich einfach alle Methoden aus den C-Dateien von einer Prolog Implementierung, in einem Python Paket anbinde. Dies ließe sich ja theoretisch über ctypes oder Cython lösen, doch was ist jetzt die bessere Lösung?

Ich hab bereits auf #python gefragt, doch dort hieß es immer einfach nur "benutz Cython", aber ohne wirklich zu begründen.

Was wichtig für mich ist:
- Einfaches setup des interfaces
- Platformunabhängigkeit(sowohl entwickeln als auch benutzen)
- Einfachheit des Codes(der ctypes Wrapper Code scheint mir meist sehr häßlich; aber cython ist nochmal eine extra Sprache)


Ich hab speziell die Platformunabhängigkeite erwähnt, weil ich gehört habe, es soll nicht so einfach sein unter Windows mit Cython eine C-Erweiterung für Python zu kompilieren. Auch wenn man MinGW installiert, denn anscheinend müsste man dann python etc auch mit gcc kompilieren. Ich selbst weiß nicht ob das stimmt, denn ich benutz kein Windows. Wäre nett wenn mir da jemand Auskunft geben könnte.

Danke im voraus
mfg naeg
lunar

@naeg: Sofern die anzubindende Bibliothek eine reine C-Bibliothek ist, halte ich ctypes für die bessere Wahl. Ich sehe den Sinn von cython auch nicht so sehr darin, Anbindungen zu schreiben, sondern eher darin, Python-Quelltext durch Übersetzung in C zu beschleunigen.

Eine Anbindung auf Basis von ctypes kommt mit reinem Python und den Mitteln der Standardbibliothek aus, cython dagegen braucht immer einen Compiler. Das kann selbst unter Linux, wo Compilieren nun nichts ungewöhnliches ist, problematisch sein, beispielsweise auf einen angemieteten Webserver, wo die Administratorrechte zur Nachinstallation des Compilers und der Header-Dateien von CPython fehlen. Zudem gibt es ja dank komfortabler Distributionen wie Ubuntu auch viele Linux-Nutzer, die gar nicht mehr so genau wissen, was kompilieren ist, sich aber dennoch an Python versuchen :)

Wiewohl sowohl ctypes als auch cython theoretisch plattformunabhängig sind, ist der Einsatz von ctypes auf vielen Plattformen aus genanntem Grund wesentlich einfacher, sowohl für Python-Entwickler als auch für spätere Nutzer eine mithilfe Deiner Anbindung geschriebenen Anwendung.

Ich persönlich kann Deine Auffassung, ctypes-Quelltext wäre „hässlich“ nicht nachvollziehen. Im Rahmen der Grenzen, welche die Ausdrucksschwäche von C setzt, ist ctypes recht schön, und vor allem verhältnismäßig einfach zu benutzen. C ist nun mal eine ziemlich schwache Sprache, cython macht sie auch nicht schöner.
BlackJack

@naeg: Also ich würde ja `ctypes` den Vorzug geben -- eben weil man da nichts kompilieren muss. Bei Cython müsste man ja entweder für alle Python-Versionen, die Endbenutzer verwenden möchten, ein Binärpaket für verschiedene Plattformen bereits stellen -- also zumindest für Windows, weil da die Benutzer es in der Regel überhaupt nicht gewohnt sind zum Installieren einen C-Compiler zur Hand zu haben.

Gegen `ctypes` sprechen in der Regel zu komplexe Headerdateien, die sich nur mühsam in Python-Code umschreiben lassen, und wenn der Overhead der dynamischen Aufrufe zu gross wird.
Benutzeravatar
naeg
User
Beiträge: 33
Registriert: Dienstag 27. April 2010, 11:53

Derzeit tendier ich auch eher zu ctypes.
BlackJack hat geschrieben: Gegen `ctypes` sprechen in der Regel zu komplexe Headerdateien, die sich nur mühsam in Python-Code umschreiben lassen, ...
Gibt es da nicht Skripte die das generieren? Ich hab zuerst ctypesgen auf Google Code gefunden, doch das produziert sehr viel Code, und nicht nur reine Funktionsanbindungen(was eig. fast alles ist was ich brauche).
Jedoch gibts es auch noch Skripts von ctypes direkt. (http://starship.python.net/crew/theller ... degen.html)
Ich hab mir letzteres noch nicht genau angesehen, werde ich aber heute hoffentlich noch machen.

Danke schonmal für eure Meinungen, bin noch offen für weitere :)
mfg naeg
BlackJack

@naeg: Die Skripte von `ctypes` sind wohl schon ziemlich alt und werden nicht mehr weiter entwickelt.

Ich musste bis jetzt noch keine wirklich grosse API anbinden und mag handgeschriebenen Code eigentlich lieber als solche generierten Quelltextmonster.

Ausserdem mache ich da in der Regel auch Sachen, die ein automatisches Werkzeug wohl nicht alle leisten kann.

Wenn die C-Funktionen und Konstanten zum Beispiel einen Prefix haben, weil C keine Namensräume kennt, dann entferne ich den in der Regel.

Ich weise bei Funktionen die Fehlerwerte zurück geben auch gerne das `errcheck`-Attribut zu, um Fehlerwerte in Ausnahmen umzuwandeln.

Ich baue oft eine "pythonische" API darauf auf, und einige Funktionen kann man direkt anbieten und andere sind Bausteine für weitere Funktionen oder Methoden. Da muss man also entscheiden ob ein `_` vor den Namen kommt, oder nicht.
Benutzeravatar
naeg
User
Beiträge: 33
Registriert: Dienstag 27. April 2010, 11:53

Das ist mir auch gerade aufgefallen.

Ich glaube ich werde es dann halt von Hand machen. Aber eine Frage habe ich noch:

Wenn ich eine lib mit CDLL() laden will, dann wird die lib scheinbar nur im LD_LIBRARY_PATH gesucht, und somit wird meine lib im Ordner /usr/lib/swipl-VERSION/lib/ARCH/ nicht gefunden. Verlangen einen Symlink zu erstellen scheint mir nicht so gut, aber pkg-config sollte die lib ja finden(tut es bei mir). Soll ich nun einfach mit subprocess pkg-config aufrufen und so den Pfad rausfinden?

Hab dazu auch das hier gefunden: http://www.python-forum.de/viewtopic.php?f=11&t=19167
mfg naeg
lunar

pkg-config ist eigentlich nicht zuständig für das Auffinden von Bibliotheken zur Laufzeit eines Programms. Es gibt lediglich die für den Compiler und den Linker benötigten Flags aus, ist also eher für die Übersetzung eines Programms gedacht. Man kann pkg-config zwar für diesen Zweck (miss-)brauchen, aber das muss nicht funktionieren. pkg-config ist beileibe nicht immer installiert, vor allem nicht unter Windows.

Bibliotheken, die auch für Drittanwendungen gedacht sind und nicht lediglich internen Zwecken dienen, sollten eigentlich im Pfad des Linkers liegen.
BlackJack

@naeg: Findet `ctypes.util.find_library()` die Bibliothek denn? Und bis Du sicher, dass Du keine `/usr/lib/libpl.so.{version}` hast? Ich habe das jedenfalls (Ubuntu).
lunar

@BlackJack: Unter Arch installiert das Paket für SWI-Prolog keine "libpl", und "find_library()" findet "libswipl" auch nicht.
BlackJack

@lunar: Und wo wird da die .so installiert? Und wird der Pfad in die Binärdatei vom Prolog-Interpreter einkompiliert oder gar die Bibliothek statisch kompiliert? Im letzten Fall könnte man da von `ctypes` aus ja gar nicht heran kommen. Im Äquivalent von `/usr/lib/swipl-VERSION/lib/ARCH/` liegt bei mir auch nur eine Bibliothek zum statischen Linken (`libpl.a`).
lunar

@BlackJack: swipl ist in der Tat nur eine statische Bibliothek, darauf hatte ich gar nicht geachtet. Eine dynamische Bibliothek namens libpl gibt es dagegen gar nicht. Offenbar ist das Paket unter Arch nicht so kompiliert worden, dass es eine dynamische Bibliothek installiert. Ich schätze mal, dass der Verwalter dieses Pakets schlicht nur vergessen hat, diese Option bei der Übersetzung zu aktivieren.

Ach ja, es gibt mit pyswip bereits eine Anbindung an SWI Prolog über ctypes. Die wird zwar nicht mehr gewartet, aber man könnte ja darauf aufbauen (sofern die Lizenz kein Problem darstellt).
Benutzeravatar
naeg
User
Beiträge: 33
Registriert: Dienstag 27. April 2010, 11:53

lunar hat geschrieben:@BlackJack: swipl ist in der Tat nur eine statische Bibliothek, darauf hatte ich gar nicht geachtet. Eine dynamische Bibliothek namens libpl gibt es dagegen gar nicht. Offenbar ist das Paket unter Arch nicht so kompiliert worden, dass es eine dynamische Bibliothek installiert. Ich schätze mal, dass der Verwalter dieses Pakets schlicht nur vergessen hat, diese Option bei der Übersetzung zu aktivieren.
Da ich ebenfalls ein Archer bin, kann ich dir sagen, dass du da nicht ganz richtig liegst ;)
Die shared library befindet sich, bei mir, in /usr/lib/swipl-5.10.2/lib/x86_64-linux/libswipl.so. Jedoch findet CDLL hier nicht, pkg-config schon.
Bei Arch heißt die lib eben libswipl, nicht libpl. Aber das Paket wird ganz normal mit ./configure, make und make install installiert, daher vermute ich, dass swi-prolog die lib umbenannt hat.(Hier läuft Version 5.10.2)
lunar hat geschrieben:Ach ja, es gibt mit pyswip bereits eine Anbindung an SWI Prolog über ctypes. Die wird zwar nicht mehr gewartet, aber man könnte ja darauf aufbauen (sofern die Lizenz kein Problem darstellt).
Genau an diesem Projekt hab ich begonnen zu arbeiten ;)

Übrigens, find_library() ist was ich brauche und funktioniert:

Code: Alles auswählen

>>> from ctypes.util import find_library
>>> find_library("swipl")
'libswipl.so.5.10.2
Jetzt stellt sich nur noch die Frage wie ich die lib lade, denn CDLL(find_library("swipl")) funktioniert nicht, da find_library() unter Linux keinen kompletten Pfad zurückgibt?
mfg naeg
lunar

@naeg: Du kannst ruhig davon ausgehen, dass ich mir den Inhalt des Bibliotheksverzeichnisses der Prolog-Installation angeschaut habe, bevor ich meinen Beitrag verfasst habe. Auf meinem System existiert keine dynamische Bibliothek namens "swipl":

Code: Alles auswählen

$ find /usr/lib/swipl-5.10.2/lib/i686-linux/ -name '*swipl*'
/usr/lib/swipl-5.10.2/lib/i686-linux/libswipl.a
Offenbar gibt es einen Unterschied zwischen den Paketen für 32- und 64-Bit Architekturen.

Was die Rückgabe von "find_library()" angeht, so kannst Du diese einfach an "CDLL" übergeben, unabhängig davon, ob der Pfad absolut ist oder nicht. Relative Pfade sind relativ zu den Suchverzeichnissen des Laufzeitbinders, und werden daher problemlos auf die entsprechenden Bibliotheksdateien abgebildet. Achte allerdings darauf, die Rückgabe von "find_library()" gegen "None" zu prüfen. "find_library()" gibt diesen Wert zurück, falls die Bibliothek nicht gefunden wurde, allerdings kommt "CDLL" damit nicht zurecht. Somit könnte eine Funktion zum Laden der Bibliothek etwa so aussehen:

Code: Alles auswählen

def load_prolog_library():
    for candidate in ('swipl', 'pl'):
        full_name = find_library(candidate)
        if full_name:
            break
    else:
         raise ImportError('No library named pl or swipl')
    return CDLL(full_name)
In pyswip könnte man übrigens erst einmal aufräumen. Die Namenskonvention der Schnittstelle entspricht nicht PEP 8, es fehlen Docstrings und Unittests, "easy.py" an manchen Stellen umständlich, "core.py" ist ein einziges Durcheinander ... dafür, dass der Autor so viel Aufhebens um den richtigen Maintainer macht, ist der Quelltext ziemlich unschön.
Benutzeravatar
naeg
User
Beiträge: 33
Registriert: Dienstag 27. April 2010, 11:53

lunar hat geschrieben:@naeg: Du kannst ruhig davon ausgehen, dass ich mir den Inhalt des Bibliotheksverzeichnisses der Prolog-Installation angeschaut habe, bevor ich meinen Beitrag verfasst habe. Auf meinem System existiert keine dynamische Bibliothek namens "swipl":

Code: Alles auswählen

$ find /usr/lib/swipl-5.10.2/lib/i686-linux/ -name '*swipl*'
/usr/lib/swipl-5.10.2/lib/i686-linux/libswipl.a
Offenbar gibt es einen Unterschied zwischen den Paketen für 32- und 64-Bit Architekturen.
Hab extra 32 und 64 bit Paket runtergeladen und entpackt - tatsächlich gibts die lib nur in dem x86_64 Paket. Eben mal bei SWI-Prolog nachgefragt.
lunar hat geschrieben: Was die Rückgabe von "find_library()" angeht, so kannst Du diese einfach an "CDLL" übergeben, unabhängig davon, ob der Pfad absolut ist oder nicht. Relative Pfade sind relativ zu den Suchverzeichnissen des Laufzeitbinders, und werden daher problemlos auf die entsprechenden Bibliotheksdateien abgebildet. Achte allerdings darauf, die Rückgabe von "find_library()" gegen "None" zu prüfen. "find_library()" gibt diesen Wert zurück, falls die Bibliothek nicht gefunden wurde, allerdings kommt "CDLL" damit nicht zurecht.
Also ist es nicht anders möglich, als einen Symlink in /usr/lib zu erstellen?

Ich glaube ich werde es einfach mit find_library() probieren(mit "pl" und "swipl") und wenn das nicht funktioniert, missbrauche ich zur Not pkg-config, sollte das auch nicht funktionieren, gibt es eine Meldung die auffordert einen Symlink in /usr/lib zu erstellen.
lunar hat geschrieben: In pyswip könnte man übrigens erst einmal aufräumen. Die Namenskonvention der Schnittstelle entspricht nicht PEP 8, es fehlen Docstrings und Unittests, "easy.py" an manchen Stellen umständlich, "core.py" ist ein einziges Durcheinander ... dafür, dass der Autor so viel Aufhebens um den richtigen Maintainer macht, ist der Quelltext ziemlich unschön.
Danke für den Hinweis. Werde mich darum noch kümmern, der ganze Teil mit den ctypes muss sowieso noch überarbeitet werden, denn unter x86_64 gibts segfaults beim assertz().
mfg naeg
lunar

@raed: Ich habe nichts gesagt von einem Symlink nach "/usr/lib/". "/usr/lib/" ist nicht der einzige Pfad, in dem der Laufzeitbinder nach Bibliotheken sucht. Hast Du denn überhaupt probiert, die Rückgabe von "find_library()" einfach an "CDLL()" zu übergeben, um die Bibliothek zu laden, so wie in meinem Beispiel gezeigt?

Die Entwickler von SWI-Prolog selbst sind im Übrigen die falsche Anlaufstelle für dieses Problem. Das ist erst einmal Sache des Maintainers des entsprechenden Pakets in Arch Linux.
Benutzeravatar
naeg
User
Beiträge: 33
Registriert: Dienstag 27. April 2010, 11:53

lunar hat geschrieben:@naeg: Ich habe nichts gesagt von einem Symlink nach "/usr/lib/". "/usr/lib/" ist nicht der einzige Pfad, in dem der Laufzeitbinder nach Bibliotheken sucht. Hast Du denn überhaupt probiert, die Rückgabe von "find_library()" einfach an "CDLL()" zu übergeben, um die Bibliothek zu laden, so wie in meinem Beispiel gezeigt?
Nein, das funktioniert nicht.

Ich hab selbst einen Symlink in /usr/lib erstellt, dann sieht es wie folgt aus:

Code: Alles auswählen

>>> find_library("swipl")
'libswipl.so.5.10.2'
>>> CDLL(find_library("swipl"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.2/ctypes/__init__.py", line 340, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: libswipl.so.5.10.2: cannot open shared object file: No such file or directory
>>> CDLL("libswipl.so")
<CDLL 'libswipl.so', handle ... at ...>
Ohne Symlink in /usr/lib findet weder find_library("swipl") noch CDLL("libswipl.so") die lib nicht. Aber mit symlink löst find_library scheinbar den symlink auf und gibt mir nur den Namen der lib in /usr/lib/swipl-<...> zurück, welchen CDLL dann nicht findet.

Der Symlink:

Code: Alles auswählen

lrwxrwxrwx 1 root root 50  8. Mär 17:18 /usr/lib/libswipl.so -> /usr/lib/swipl-5.10.2/lib/x86_64-linux/libswipl.so
lunar hat geschrieben: Die Entwickler von SWI-Prolog selbst sind im Übrigen die falsche Anlaufstelle für dieses Problem. Das ist erst einmal Sache des Maintainers des entsprechenden Pakets in Arch Linux.
Ich hab in #archlinux.de nachgefragt, weil ich mir nicht sicher war und da hat man mir gesagt, dass die symlinks in /usr/lib Aufgabe des Projekts sind, nicht der Distribution. Jedoch habe ich mit Jan von SWI-Prolog zuvor schon wegen PySWIP geschrieben, und er hat gesagt er wolle mir helfen wo er kann.
mfg naeg
lunar

Ich nehme mal an, dass Du eine symbolische Verknüpfung auf eine weitere symbolische Verknüpfung erstellt hast, und "find_library()" „zu weit“ auflöst. Versuche mal, die Verknüpfung direkt auf die Bibliothek mit dem vollständigen Namen der Bibliothek anzulegen, also:

Code: Alles auswählen

/usr/lib/libswipl.so.5.10.2 -> /usr/lib/swipl-5.10.2/lib/x86_64-linux/libswipl.so.5.10.2
Und was das Paket angeht, wieso fragst Du überall, nur nicht den, der sich mit dem Paket selbst auskennt, nämlich den Maintainer? Dessen Mailadresse steht ja nicht aus Spaß an der Freude im PKGBUILD und in der Paketdatenbank ;)
Benutzeravatar
naeg
User
Beiträge: 33
Registriert: Dienstag 27. April 2010, 11:53

lunar hat geschrieben:Ich nehme mal an, dass Du eine symbolische Verknüpfung auf eine weitere symbolische Verknüpfung erstellt hast, und "find_library()" „zu weit“ auflöst. Versuche mal, die Verknüpfung direkt auf die Bibliothek mit dem vollständigen Namen der Bibliothek anzulegen, also:

Code: Alles auswählen

/usr/lib/libswipl.so.5.10.2 -> /usr/lib/swipl-5.10.2/lib/x86_64-linux/libswipl.so.5.10.2
Wenn ich den symlink so erstelle:

Code: Alles auswählen

>>> find_library("swipl")
>>> find_library("swipl.5.10.2")
>>> find_library("swipl.so.5.10.2")
>>> find_library("libswipl.so.5.10.2")
>>> find_library("swipl.so")
Die lib kann ich zwar mit CDLL("libswipl.so.5.10.2") laden, aber eben nur damit.
lunar hat geschrieben:Und was das Paket angeht, wieso fragst Du überall, nur nicht den, der sich mit dem Paket selbst auskennt, nämlich den Maintainer? Dessen Mailadresse steht ja nicht aus Spaß an der Freude im PKGBUILD und in der Paketdatenbank ;)
Dem hab ich auch bereits eine Email geschrieben, er hat mir nur gesagt, dass die shared libs installiert sind und gab mir den Output von pkg-config mit(dadurch bin ich eben auf die Idee gekommen pkg-config zu verwenden).
mfg naeg
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Da ist sie wieder, die großartige Qualität von Arch-Paketen :oops: Ich würde, um ehrlich zu sein, einfach einen Bugreport an Arch schreiben und sie sonst ignorieren. Sie können sich ja wieder melden wenn sie ihren Murks ausgebessert haben.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
naeg
User
Beiträge: 33
Registriert: Dienstag 27. April 2010, 11:53

Leonidas hat geschrieben:Da ist sie wieder, die großartige Qualität von Arch-Paketen :oops: Ich würde, um ehrlich zu sein, einfach einen Bugreport an Arch schreiben und sie sonst ignorieren. Sie können sich ja wieder melden wenn sie ihren Murks ausgebessert haben.
Was hat das mit der Qualität von Arch Linux Paketen zu tun?
Arch Linux will alle Pakete möglichst unmodifiziert an Upstream halten. Da SWI-Prolog keinen Symlink in /usr/lib (o.ä.) erstellt, gibts diesen symlink eben nicht. Wenn man mal kurz nachdenkt ist die Idee mit an Upstream halten nicht blöd ;)

Und das mit den shared libs: Es ist tatsächlich so, dass die x86 Version die shared lib nicht standardmäßig dabei hat, aber die x86_64 schon. Der Grund dafür ist, dass die x86 mit shared libs laut Entwickler ca. 10% langsamer ist.

Jan hat mir auch noch einen Tipp gegeben. Mit "swipl --dump-runtime-variables" kann ich mir den Pfad zur lib zusammenbasteln. Dies funktioniert aber wiederum nur, wenn swipl in PATH ist, was ich bei Windows ja nicht voraussetzen kann.
mfg naeg
Antworten