Owijanie biblioteki C w Pythonie: C, Cython lub ctypes?

Chcę wywołać bibliotekę C z aplikacji Pythona. Nie chcę owijać całego API, tylko funkcje i typy danych, które są istotne dla mojego przypadku. Jak widzę, mam trzy wyjścia:

  1. utworzyć rzeczywisty moduł rozszerzeń w C. prawdopodobnie przesada, I chciałbym również uniknąć kosztów nauki pisania rozszerzeń.
  2. użyj Cython , Aby udostępnić odpowiednie części z biblioteki C Pythonowi.
  3. zrobić całość w Pythonie, używając ctypes aby komunikować się z biblioteką zewnętrzną.
Nie jestem pewien, czy 2) Czy 3) jest lepszym wyborem. Zaletą 3) jest to, że ctypes jest częścią biblioteki standardowej, a wynikowy kod byłby czystym Pythonem-chociaż nie jestem pewien, jak duża jest ta zaleta.

Czy jest więcej zalet / wad z którymkolwiek wyborem? Jakie podejście polecacie?


Edit: Dzięki za wszystkie odpowiedzi, stanowią one dobry zasób dla każdego, kto chce zrobić coś podobnego. Decyzja, oczywiście, jest jeszcze do podjęcia dla pojedynczego przypadku-nie ma żadnej odpowiedzi" to jest właściwa rzecz". Dla mojego własnego przypadku, prawdopodobnie pójdę z ctypes, ale również czekam na wypróbowanie Cython w innym projekcie.

Ponieważ nie ma jednej prawdziwej odpowiedzi, akceptacja jednej jest nieco arbitralna; wybrałem odpowiedź FogleBird, ponieważ zapewnia ona dobry wgląd w ctypy i obecnie jest również najczęściej głosowaną odpowiedzią. Proponuję jednak przeczytać wszystkie odpowiedzi, aby uzyskać dobry przegląd.

Jeszcze raz dziękuję.
Author: balpha, 2009-12-21

12 answers

ctypes to twoja najlepsza szansa na szybkie wykonanie tego zadania, a praca z nim to przyjemność, ponieważ wciąż piszesz Pythona!

Niedawno zapakowałemFTDI sterownik do komunikacji z chipem USB za pomocą ctypes i było świetnie. Wszystko zrobiłem i pracowałem w mniej niż jeden dzień roboczy. (Zaimplementowałem tylko potrzebne nam funkcje, około 15 funkcji).

Wcześniej korzystaliśmy z zewnętrznego modułu, PyUSB , w tym samym celu. PyUSB jest prawdziwym C / Pythonem moduł rozszerzenia. Ale PyUSB nie wypuszczał GIL podczas blokowania odczytów/zapisów, co sprawiało nam problemy. Więc napisałem nasz własny moduł używając ctypes, który zwalnia GIL podczas wywoływania natywnych funkcji.

Należy zauważyć, że ctypes nie będzie wiedział o stałych #define i innych rzeczach w bibliotece, której używasz, tylko o funkcjach, więc będziesz musiał przedefiniować te stałe w swoim własnym kodzie.

Oto przykład jak zakończył się kod (wiele wycinków out, po prostu staram się pokazać sedno tego):

from ctypes import *

d2xx = WinDLL('ftd2xx')

OK = 0
INVALID_HANDLE = 1
DEVICE_NOT_FOUND = 2
DEVICE_NOT_OPENED = 3

...

def openEx(serial):
    serial = create_string_buffer(serial)
    handle = c_int()
    if d2xx.FT_OpenEx(serial, OPEN_BY_SERIAL_NUMBER, byref(handle)) == OK:
        return Handle(handle.value)
    raise D2XXException

class Handle(object):
    def __init__(self, handle):
        self.handle = handle
    ...
    def read(self, bytes):
        buffer = create_string_buffer(bytes)
        count = c_int()
        if d2xx.FT_Read(self.handle, buffer, bytes, byref(count)) == OK:
            return buffer.raw[:count.value]
        raise D2XXException
    def write(self, data):
        buffer = create_string_buffer(data)
        count = c_int()
        bytes = len(data)
        if d2xx.FT_Write(self.handle, buffer, bytes, byref(count)) == OK:
            return count.value
        raise D2XXException

Ktoś zrobił jakieś benchmarki na różnych opcjach.

Byłbym bardziej niezdecydowany, gdybym musiał zawinąć bibliotekę C++ z dużą ilością klas/szablonów / itp. Ale ctypes działa dobrze ze strukturami i może nawet callback do Pythona.

 119
Author: FogleBird,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-21 20:58:03

Uwaga: opinia dewelopera Cython core przed nami.

Prawie zawsze polecam Cython nad ctyp. Powodem jest to, że ma znacznie płynniejszą ścieżkę aktualizacji. Jeśli używasz ctypes, wiele rzeczy będzie na początku proste, a na pewno fajnie jest napisać kod FFI w prostym Pythonie, bez kompilacji, budowania zależności i tego wszystkiego. Jednak w pewnym momencie, prawie na pewno okaże się, że trzeba wywołać do biblioteki C dużo, albo w pętli lub w dłuższej serii / align = "center" bgcolor = "# e0ffe0 " / cesarz chin / / align = center / W tym momencie zauważysz, że nie możesz tego zrobić z ctypami. Lub, gdy potrzebujesz funkcji callback i okaże się, że Python kod callback staje się wąskim gardłem, chcesz go przyspieszyć i / lub przenieść go w dół do C, jak również. Ponownie, nie można tego zrobić z ctypes. Musisz więc w tym momencie zmienić języki i zacząć przepisywać części kodu, potencjalnie inżynierii odwrotnej kodu Pythona/ctypes do zwykłego C, w ten sposób psucie całej korzyści z pisania kodu w prostym Pythonie w pierwszej kolejności.

Z Cythonem, OTOH, możesz zrobić kod zawijający i wywołujący tak cienki lub gruby, jak chcesz. Możesz zacząć od prostych wywołań do kodu C ze zwykłego kodu Pythona, a Cython przełoży je na natywne wywołania w języku C, bez dodatkowych kosztów wywołania i z bardzo niskim obciążeniem konwersji dla parametrów Pythona. Gdy zauważysz, że potrzebujesz jeszcze większej wydajności w w pewnym momencie, gdy wykonujesz zbyt wiele kosztownych połączeń do biblioteki C, możesz zacząć opisywać otaczający cię Kod Pythona za pomocą typów statycznych i pozwolić, aby Cython zoptymalizował go prosto do C dla Ciebie. Możesz też zacząć przepisywać części kodu C w Cythonie, aby uniknąć połączeń i specjalizować się i dokręcać pętle algorytmicznie. A jeśli potrzebujesz szybkiego wywołania zwrotnego, po prostu napisz funkcję z odpowiednim podpisem i przekaż ją bezpośrednio do rejestru wywołania zwrotnego C. Ponownie, nie napowietrznych, a to daje prostą wydajność wywołania C. A w znacznie mniej prawdopodobnym przypadku, że naprawdę nie możesz uzyskać kodu wystarczająco szybko w Cythonie, nadal możesz rozważyć przepisanie naprawdę krytycznych jego części w C (lub C++ lub Fortran) i wywołać go z kodu Cython naturalnie i natywnie. Ale wtedy to naprawdę staje się ostatecznością, a nie jedyną opcją.

Więc ctypes jest miły zrobić proste rzeczy i szybko dostać coś działa. Jednak, jak tylko rzeczy zaczynają rozwijaj się, najprawdopodobniej dojdziesz do punktu, w którym zauważysz, że lepiej używaj Cythona od samego początku.

 158
Author: Stefan Behnel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-16 13:41:45

Cython jest całkiem fajnym narzędziem samo w sobie, warto się go nauczyć i jest zaskakująco blisko składni Pythona. Jeśli robisz jakieś obliczenia naukowe z Numpy, to Cython jest dobrym rozwiązaniem, ponieważ integruje się z Numpy w celu szybkich operacji matrycowych.

Cython to superset języka Python. Możesz rzucić na niego dowolny poprawny plik Pythona, a wypluje on poprawny program C. W takim przypadku Cython mapuje wywołania Pythona do bazowego API CPython. Skutkuje to być może 50% przyspieszenie, ponieważ twój kod nie jest już interpretowany.

Aby uzyskać pewne optymalizacje, musisz zacząć opowiadać Cythonowi dodatkowe fakty na temat kodu, takie jak deklaracje typu. Jeśli powiesz to wystarczająco, może to doprowadzić kod do czystego C. Oznacza to, że pętla for w Pythonie staje się pętlą for W C. Tutaj zobaczysz ogromny wzrost prędkości. Możesz również link do zewnętrznych programów C tutaj.

Używanie kodu Cythona jest również niezwykle łatwe. Myślałem, że Instrukcja to utrudnia. Ty dosłownie po prostu zrób:

$ cython mymodule.pyx
$ gcc [some arguments here] mymodule.c -o mymodule.so

I wtedy możesz import mymodule w swoim kodzie Pythona i zapomnieć całkowicie, że kompiluje się do C.

W każdym razie, ponieważ Cython jest tak łatwy w konfiguracji i użyciu, sugeruję wypróbowanie go, aby sprawdzić, czy odpowiada twoim potrzebom. Nie będzie to strata, jeśli okaże się, że nie jest to narzędzie, którego szukasz.

 100
Author: carl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-22 18:18:43

Do wywołania biblioteki C z aplikacji Pythona jest również cffi która jest nową alternatywą dla ctypów. Przynosi świeże spojrzenie na FFI:

  • rozwiązuje problem w fascynujący, czysty sposób (w przeciwieństwie do ctypów)
  • nie wymaga pisania kodu innego niż Python (jak w SWIG, Cython ,...)
 42
Author: Robert Zaremba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-18 17:33:30

Wrzucę jeszcze jeden: SWIG

Jest łatwy do nauczenia, robi wiele rzeczy dobrze i obsługuje wiele innych języków, więc czas spędzony na nauce może być całkiem przydatny.

Jeśli używasz SWIG, tworzysz nowy moduł rozszerzeń Pythona, ale SWIG wykonuje większość ciężkich prac za Ciebie.

 21
Author: Chris Arguin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-21 20:09:31

Osobiście napisałbym moduł rozszerzenia w C. Nie daj się zastraszyć rozszerzeniom Pythona C - wcale nie są trudne do napisania. Dokumentacja jest bardzo przejrzysta i pomocna. Kiedy po raz pierwszy napisałem rozszerzenie C w Pythonie, myślę, że zajęło mi około godziny, aby dowiedzieć się, jak je napisać-nie dużo czasu w ogóle.

 18
Author: mipadi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-21 20:36:42

Ctypes jest świetny, gdy masz już skompilowany obiekt blob biblioteki (np. biblioteki systemu operacyjnego). Narzut wywołania jest jednak poważny, więc jeśli będziesz wykonywać wiele połączeń do biblioteki, a i tak będziesz pisać kod C( lub przynajmniej go kompilować), powiedziałbym, aby przejść do cython. To nie jest dużo więcej pracy, i to będzie znacznie szybciej i bardziej pythonic, aby użyć wynikowego pliku pyd.

Osobiście używam cythona do szybkich przyspieszeń Pythona kod (pętle i porównania liczb całkowitych to dwa obszary, w których cython szczególnie świeci), a gdy pojawi się więcej zaangażowanych w kod / owijanie innych bibliotek, zwrócę się do Boost.Python . Boost.Python może być wybredny w konfiguracji, ale gdy już go uruchomisz, sprawia, że owijanie kodu C/C++ jest proste.

Cython jest też świetny w owijaniu numpy (czego nauczyłem się z SciPy 2009 ), ale nie używałem numpy, więc nie mogę komentować to.

 11
Author: Ryan Ginstrom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-27 15:13:27

Jeśli masz już bibliotekę ze zdefiniowanym API, myślę, że ctypes jest najlepszą opcją, ponieważ musisz tylko trochę zainicjować, a następnie mniej więcej wywołać bibliotekę w sposób, do którego jesteś przyzwyczajony.

Myślę, że Cython lub tworzenie modułu rozszerzeń w C (co nie jest zbyt trudne) są bardziej przydatne, gdy potrzebujesz nowego kodu, np. wywołanie tej biblioteki i wykonanie skomplikowanych, czasochłonnych zadań, a następnie przekazanie wyniku Pythonowi.

Innym podejściem, dla prostych programów, jest bezpośrednio wykonaj inny proces (skompilowany zewnętrznie), wyprowadzając wynik na standardowe wyjście i wywołując go za pomocą modułu podprocesu. Czasami jest to najłatwiejsze podejście.

Na przykład, jeśli stworzysz program konsoli C, który działa mniej więcej w ten sposób

$miCcode 10
Result: 12345678
Możesz to nazwać z Pythona
>>> import subprocess
>>> p = subprocess.Popen(['miCcode', '10'], shell=True, stdout=subprocess.PIPE)
>>> std_out, std_err = p.communicate()
>>> print std_out
Result: 12345678

Z małym formatowaniem ciągów, możesz wziąć wynik w dowolny sposób. Możesz również przechwycić standardowe wyjście błędu, więc jest ono dość elastyczne.

 11
Author: Khelben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-16 12:32:57

Jest jeden problem, który sprawił, że używałem ctype, a nie cython i który nie jest wymieniony w innych odpowiedziach.

Używanie ctypów wynik nie zależy od używanego kompilatora. Możesz napisać bibliotekę używając mniej więcej dowolnego języka, który może być skompilowany do natywnej biblioteki współdzielonej. Nie ma większego znaczenia, który system, który język i który kompilator. Cython jest jednak ograniczony infrastrukturą. Na przykład, jeśli chcesz używać kompilatora Intela w systemie windows, jest to znacznie więcej tricky to make cython work: powinieneś" wyjaśnić " kompilator do cythona, przekompilować coś za pomocą tego dokładnie kompilatora, itp. Co znacznie ogranicza przenośność.

 8
Author: Misha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-12 14:15:18

Jeśli celujesz w System Windows i chcesz owinąć niektóre zastrzeżone biblioteki C++, możesz wkrótce odkryć, że różne wersje msvcrt***.dll (Visual C++ Runtime) są nieco niekompatybilne.

Oznacza to, że możesz nie być w stanie użyć Cython ponieważ wynik wrapper.pyd jest powiązany z msvcr90.dll (Python 2.7) lub msvcr100.dll (Python 3.x) . Jeśli Biblioteka, którą owijasz, jest powiązana z inną wersją środowiska uruchomieniowego, to nie szczęście.

Następnie, aby wszystko działało, musisz utworzyć wrappery C dla bibliotek C++, połączyć to Wrapper dll z tą samą wersją msvcrt***.dll co Twoja biblioteka C++. A następnie użyć ctypes aby dynamicznie załadować ręcznie zwijaną bibliotekę DLL wrapper w czasie wykonywania.

Istnieje więc wiele drobnych szczegółów, które zostały szczegółowo opisane w poniższym artykule:

"Beautiful Native Libraries (in Python) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/

 4
Author: iljau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-23 11:36:34

Wiem, że to stare pytanie, ale to coś pojawia się w google, gdy przeszukujesz rzeczy takie jak ctypes vs cython, a większość odpowiedzi tutaj są napisane przez tych, którzy są biegli już w cython lub c, które mogą nie odzwierciedlać rzeczywistego czasu potrzebnego do zainwestowania, aby nauczyć się tych, aby wdrożyć swoje rozwiązanie. Jestem całkowicie początkujący w obu. Nigdy wcześniej nie dotknąłem cython i mam bardzo małe doświadczenie na c/c++.

Przez ostatnie dwa dni szukałem sposobu na delegowanie wydajność ciężka część mojego kodu do czegoś bardziej niskiego poziomu niż python. Zaimplementowałem mój kod zarówno w ctypes, jak i Cython, który składał się zasadniczo z dwóch prostych funkcji.

Miałem ogromną listę łańcuchów , która musiała zostać przetworzona. Notice list i string. Oba typy nie odpowiadają dokładnie typom w c, ponieważ ciągi Pythona są domyślnie unicode, a c nie. Listy w Pythonie nie są po prostu tablicami c.

Oto mój werdykt. Użyj cython. Informatyka integruje się bardziej płynnie z Pythonem i ogólnie łatwiej z nim pracować. Jeśli coś pójdzie nie tak ctypes po prostu rzuca ci segfault, przynajmniej cython da ci Ostrzeżenia kompilacji ze śladem stosu, kiedy tylko jest to możliwe, i możesz łatwo zwrócić poprawny obiekt Pythona za pomocą cython.

Oto szczegółowy opis, ile czasu potrzebowałem, aby zainwestować w oba, aby zaimplementować tę samą funkcję. Tak przy okazji, bardzo mało zajmowałem się programowaniem C / C++:

  • Ctypes:

    • Około 2h na badaniu, jak przekształcić moją listę ciągów unicode na typ zgodny z C.
    • około godziny, jak prawidłowo zwrócić łańcuch znaków z funkcji c. Tutaj rzeczywiście podałem własne rozwiązanie więc kiedy już napisałem funkcje.
    • około pół godziny na napisanie kodu w c, skompilowanie go do dynamicznej biblioteki.
    • 10 minut na napisanie kodu testowego w Pythonie, aby sprawdzić, czy kod c działa.
    • około godziny pracy kilka testów i przestawianie kodu c.
    • następnie podłączyłem kod c do rzeczywistej bazy kodu i zobaczyłem, że ctypes nie gra dobrze z modułem multiprocessing, ponieważ jego obsługa nie jest domyślnie wybierana.
    • około 20 minut przearanżowałem mój kod, aby nie używać multiprocessing modułu, i powtórzyłem.
    • następnie druga funkcja w moim kodzie c wygenerowała segfaults w mojej bazie kodu, chociaż przeszła mój kod testowy. To pewnie moja wina, że nie sprawdzałem dobrze spraw edge, byłem Szukam szybkiego rozwiązania.
    • Przez około 40 minut próbowałem ustalić możliwe przyczyny tych segfaults.
  • podzieliłem swoje funkcje na dwie biblioteki i spróbowałem ponownie. Nadal miałem segfaults dla mojej drugiej funkcji.
  • postanowiłem puścić drugą funkcję i używać tylko pierwszej funkcji c kodu i na drugiej lub trzeciej iteracji pętli Pythona, która go używa, miałem UnicodeError o nie dekodowaniu bajtu w pewnej pozycji, chociaż zakodowałem i dekodowałem wszystko jasne.
W tym momencie postanowiłem poszukać alternatywy i postanowiłem zajrzeć do cython:
  • Cython
    • 10 min czytania cython hello world .
    • 15 min sprawdzania więc Jak używać cythona z setuptools zamiast distutils.
    • 10 min czytania na typach cython i typach Pythona. Nauczyłem się, że mogę używać większości wbudowanych typów Pythona do statycznego pisania.
    • 15 min o ponownym wpisaniu kodu Pythona z typami cythona.
    • 10 min modyfikacji mojego setup.py aby użyć skompilowanego modułu w mojej bazie kodowej.
    • podłączyłem moduł bezpośrednio do multiprocessing wersji codebase. To działa.

Dla przypomnienia, oczywiście nie mierzyłem dokładnych czasów mojej inwestycji. Może to być bardzo dobrze, że moje postrzeganie czasu było trochę uważny ze względu na wysiłek umysłowy wymagane podczas miałem do czynienia z ctypes. Ale powinno przekazać poczucie radzenia sobie z cython i ctypes

 3
Author: Kaan E.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-08 04:10:03

Istnieje również możliwość użycia GObject Introspectiondla bibliotek, które używają GLib.

 2
Author: plaes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-01 05:19:39