Chcę wywołać bibliotekę C z aplikacji Python. Nie chcę zawijać całego interfejsu API, tylko funkcje i typy danych, które są odpowiednie w moim przypadku. Widzę, że mam trzy możliwości:
- Utwórz rzeczywisty moduł rozszerzeń w C. Prawdopodobnie przesada, a także chciałbym uniknąć narzutu związanego z nauką pisania rozszerzeń.
- Użyj Cython, aby udostępnić odpowiednie części z biblioteki C Pythonowi.
- Zrób wszystko w Pythonie, używając
ctypes
do komunikacji z biblioteką zewnętrzną.
Nie jestem pewien, czy 2) czy 3) jest lepszym wyborem. Zaletą 3) jest to, że ctypes
jest częścią standardowej biblioteki, a wynikowy kod byłby czystym Pythonem - chociaż nie jestem pewien, jak duża jest ta zaleta.
Czy są jakieś zalety / wady w obu przypadkach? Jakie podejście polecasz?
Edycja: Dzięki za wszystkie odpowiedzi, stanowią one dobry zasób dla każdego, kto chce zrobić coś podobnego. Oczywiście, decyzja musi zostać jeszcze podjęta w sprawie pojedynczej sprawy - nie ma jednej odpowiedzi typu „to jest właściwa”. W moim przypadku prawdopodobnie użyję ctypów, ale nie mogę się doczekać wypróbowania Cython w innym projekcie.
Ponieważ nie ma jednej prawdziwej odpowiedzi, zaakceptowanie jednej jest nieco arbitralne; Wybrałem odpowiedź FogleBirda, ponieważ zapewnia ona dobry wgląd w typy cypla, a obecnie jest również odpowiedzią najczęściej głosowaną. Proponuję jednak przeczytać wszystkie odpowiedzi, aby uzyskać dobry przegląd.
Dzięki jeszcze raz.
Odpowiedzi:
ctypes
jest najlepszym rozwiązaniem, aby szybko to zrobić, i przyjemnością jest pracować, gdy wciąż piszesz Python!Niedawno zapakowałem sterownik FTDI do komunikacji z układem USB za pomocą ctypów i było świetnie. Zrobiłem to wszystko i pracowałem w mniej niż jeden dzień roboczy. (Zaimplementowałem tylko te funkcje, których potrzebowaliśmy, około 15 funkcji).
Wcześniej korzystaliśmy z modułu zewnętrznego, PyUSB , w tym samym celu. PyUSB to rzeczywisty moduł rozszerzeń C / Python. Ale PyUSB nie wypuszczał GIL podczas blokowania odczytów / zapisów, co przysparzało nam problemów. Napisałem więc własny moduł przy użyciu ctypes, który zwalnia GIL podczas wywoływania funkcji natywnych.
Należy zauważyć, że ctypy nie będą wiedziały o
#define
stałych i innych rzeczach w bibliotece, której używasz, tylko o funkcjach, więc będziesz musiał przedefiniować te stałe we własnym kodzie.Oto przykład, w jaki sposób kod wyglądał (wiele wycinało, próbując pokazać sedno tego):
Ktoś zrobił kilka testów porównawczych dla różnych opcji.
Byłbym bardziej niezdecydowany, gdybym musiał owinąć bibliotekę C ++ dużą ilością klas / szablonów / etc. Ale ctypes działa dobrze z strukturami i może nawet wywoływać zwrotne w Pythonie.
źródło
ctypes
(dlapyinotify
), ale chciałbym zrozumieć problem dokładniej.One thing to note is that ctypes won't know about #define constants and stuff in the library you're using, only the functions, so you'll have to redefine those constants in your own code.
Muszę więc zdefiniować stałe, które są wwinioctl.h
…ctypes
jest znacznie wolniejszy niż rozszerzenie c, ponieważ wąskim gardłem jest interfejs Pythona do COstrzeżenie: przed nami opinia twórcy rdzenia Cython.
Prawie zawsze polecam Cython zamiast ctypów. Powodem jest to, że ma znacznie płynniejszą ścieżkę aktualizacji. Jeśli użyjesz ctypów, na początku wiele rzeczy będzie prostych i na pewno fajnie jest napisać kod FFI w zwykłym Pythonie, bez kompilacji, budowania zależności i tym podobnych. Jednak w pewnym momencie prawie na pewno okaże się, że musisz często wywoływać bibliotekę C, albo w pętli, albo w dłuższej serii współzależnych wywołań, i chciałbyś to przyspieszyć. W tym momencie zauważysz, że nie możesz tego zrobić z ctypami. Lub, gdy potrzebujesz funkcji wywołania zwrotnego i okaże się, że Twój kod wywołania w Pythonie staje się wąskim gardłem, chciałbyś go przyspieszyć i / lub przenieść również w dół do C. Ponownie, nie możesz tego zrobić z ctypami.
Z Cython, OTOH, masz całkowitą swobodę, aby owijanie i wywoływanie kodu było tak cienkie lub grube, jak chcesz. Możesz zacząć od prostych wywołań do kodu C ze zwykłego kodu Python, a Cython przetłumaczy je na rodzime wywołania C, bez dodatkowego narzutu i przy bardzo niskim narzutu konwersji parametrów Pythona. Kiedy zauważysz, że potrzebujesz jeszcze większej wydajności w pewnym momencie, w którym wykonujesz zbyt wiele drogich wywołań do swojej biblioteki C, możesz zacząć dodawać adnotacje do otaczającego kodu Python typami statycznymi i pozwolić Cython zoptymalizować go bezpośrednio w C. Lub możesz rozpocząć przepisywanie części kodu C w Cython, aby uniknąć połączeń oraz aby specjalizować się i zaciskać pętle algorytmicznie. A jeśli potrzebujesz szybkiego oddzwonienia, po prostu napisz funkcję z odpowiednim podpisem i przekaż ją bezpośrednio do rejestru zwrotnego C. Ponownie, bez narzutu, i daje to zwykłą wydajność połączeń w języku C. A w znacznie mniej prawdopodobnym przypadku, gdy naprawdę nie możesz uzyskać wystarczająco szybkiego kodu w Cython, nadal możesz rozważyć przepisanie naprawdę krytycznych części w C (lub C ++ lub Fortran) i nazwać go z kodu Cython naturalnie i natywnie. Ale tak naprawdę staje się to ostatecznością zamiast jedynej opcji.
Tak więc ctypes miło jest robić proste rzeczy i szybko coś uruchomić. Jednak jak tylko wszystko zacznie rosnąć, najprawdopodobniej dojdziesz do momentu, w którym zauważysz, że lepiej używać Cython od samego początku.
źródło
__radd__
.). Jest to szczególnie denerwujące, gdy planujesz, aby twoja klasa współdziałała z wbudowanymi typami (np.int
Ifloat
). Ponadto, magiczne metody w cytonie są w ogóle trochę błędne.Cython jest całkiem fajnym narzędziem samym w sobie, wartym nauki i zaskakująco zbliżonym do składni Pythona. Jeśli wykonujesz jakiekolwiek obliczenia naukowe za pomocą Numpy, to Cython jest dobrym rozwiązaniem, ponieważ integruje się z Numpy w celu szybkiego działania macierzy.
Cython jest nadzbiorem języka Python. Możesz wrzucić do niego dowolny prawidłowy plik Pythona, a on wypluje prawidłowy program C. W takim przypadku Cython po prostu odwzoruje wywołania Pythona na bazowy interfejs API CPython. Powoduje to może 50% przyspieszenie, ponieważ Twój kod nie jest już interpretowany.
Aby uzyskać optymalizację, musisz zacząć informować Cython o dodatkowych faktach na temat swojego kodu, takich jak deklaracje typów. Jeśli powiesz wystarczająco dużo, może sprowadzić kod do czystego C. To znaczy, że pętla for w Pythonie staje się pętlą for w C. Tutaj zobaczysz znaczny wzrost prędkości. Możesz również link do zewnętrznych programów C.
Korzystanie z kodu Cython jest również niezwykle łatwe. Myślałem, że instrukcja utrudnia to. Dosłownie po prostu robisz:
a następnie możesz
import mymodule
w swoim kodzie Python i całkowicie zapomnieć, że kompiluje się do C.W każdym razie, ponieważ Cython jest tak łatwy w konfiguracji i zaczyna korzystać, sugeruję wypróbowanie go, aby sprawdzić, czy odpowiada Twoim potrzebom. To nie będzie strata, jeśli okaże się, że nie jest narzędziem, którego szukasz.
źródło
mymod.pyx
moduł, a następnie zrób to,import pyximport; pyximport.install(); import mymod
a kompilacja odbędzie się za kulisami.runcython mymodule.pyx
. I w przeciwieństwie do pyximport, możesz go używać do bardziej wymagających zadań łączenia. Jedynym zastrzeżeniem jest to, że jestem tym, który napisał za to 20 linii bash i może być stronniczy.Do wywoływania biblioteki C z aplikacji Python istnieje także cffi, która jest nową alternatywą dla ctypów . Daje świeży wygląd FFI:
źródło
Wyrzucę jeszcze jedną: SWIG
Łatwo się go nauczyć, robi wiele rzeczy dobrze i obsługuje wiele innych języków, więc czas spędzony na nauce może być bardzo przydatny.
Jeśli używasz SWIG, tworzysz nowy moduł rozszerzenia Pythona, ale SWIG wykonuje za ciebie większość ciężkich zadań.
źródło
Osobiście napisałbym moduł rozszerzeń w C. Nie daj się zastraszyć rozszerzeniami Python 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 wymyślić, jak je napisać - wcale nie dużo czasu.
źródło
ctypes jest świetny, gdy masz już skompilowany obiekt blob biblioteki do obsługi (taki jak biblioteki systemu operacyjnego). Narzut związany z wywoływaniem jest jednak poważny, więc jeśli będziesz wykonywać wiele wywołań do biblioteki, a mimo to będziesz pisać kod C (a przynajmniej go kompilować), powiedziałbym cython . To niewiele więcej pracy, a użycie wynikowego pliku pyd będzie znacznie szybsze i bardziej pytoniczne.
Ja osobiście używam cytonu do szybkiego przyspieszania kodu pytona (pętle i porównania liczb całkowitych to dwa obszary, w których cyton szczególnie świeci), a kiedy jest więcej zaangażowany kod / zawijanie innych bibliotek, zwrócę się do Boost.Python . Boost.Python może być skomplikowany w konfiguracji, ale gdy już zaczniesz działać, upraszcza pakowanie kodu C / C ++.
cython jest również świetny w pakowaniu numpy (czego dowiedziałem się z postępowania SciPy 2009 ), ale nie użyłem numpy, więc nie mogę tego komentować.
źródło
Jeśli masz już bibliotekę ze zdefiniowanym interfejsem API, myślę, że
ctypes
jest to najlepsza opcja, ponieważ wystarczy wykonać niewielką inicjalizację, a następnie mniej więcej wywołać bibliotekę tak, jak kiedyś.Myślę, że Cython lub tworzenie modułu rozszerzenia w C (co nie jest bardzo trudne) jest bardziej przydatne, gdy potrzebujesz nowego kodu, np. Wywoływanie tej biblioteki i wykonywanie skomplikowanych, czasochłonnych zadań, a następnie przekazywanie wyniku do Pythona.
Inne podejście, w przypadku prostych programów, polega bezpośrednio na wykonaniu innego procesu (skompilowanego zewnętrznie), wypisaniu wyniku na standardowe wyjście i wywołaniu go za pomocą modułu podprocesu. Czasami jest to najłatwiejsze podejście.
Na przykład, jeśli utworzysz program konsoli C, który działa mniej więcej w ten sposób
Możesz to nazwać z Python
Dzięki niewielkiemu formatowaniu ciągów możesz wziąć wynik w dowolny sposób. Możesz także przechwycić standardowe wyjście błędu, więc jest dość elastyczne.
źródło
shell=True
może łatwością doprowadzić do pewnego rodzaju exploita, gdy użytkownik naprawdę otrzyma powłokę. Jest w porządku, gdy programista jest jedynym użytkownikiem, ale na świecie jest mnóstwo irytujących sztuczek, które tylko czekają na coś takiego.Jest jeden problem, który kazał mi używać ctypów, a nie cytonów i który nie jest wspomniany w innych odpowiedziach.
Przy użyciu ctypów wynik nie zależy od kompilatora, którego używasz. Możesz napisać bibliotekę, używając mniej więcej dowolnego języka, który można skompilować w natywnej bibliotece współdzielonej. Nie ma większego znaczenia, który system, język i kompilator. Jednak Cython jest ograniczony infrastrukturą. Na przykład, jeśli chcesz używać kompilatora Intel w systemie Windows, znacznie trudniej jest uruchomić cyton: powinieneś „wyjaśnić” kompilator cytonowi, skompilować coś z tym dokładnym kompilatorem itp. Co znacznie ogranicza przenośność.
źródło
Jeśli celujesz w system Windows i chcesz zawinąć niektóre zastrzeżone biblioteki C ++, możesz wkrótce odkryć, że różne wersje
msvcrt***.dll
(Visual C ++ Runtime) są nieco niezgodne.Oznacza to, że możesz nie być w stanie używać,
Cython
ponieważ wynikwrapper.pyd
jest powiązany zmsvcr90.dll
(Python 2.7) lubmsvcr100.dll
(Python 3.x) . Jeśli biblioteka, którą pakujesz, jest powiązana z inną wersją środowiska wykonawczego, oznacza to, że nie masz szczęścia.Następnie, aby wszystko działało, musisz utworzyć otoki C dla bibliotek C ++, połącz dll otoki z tą samą wersją
msvcrt***.dll
co twoja biblioteka C ++. A potem użyjctypes
aby dynamicznie załadować ręcznie walcowane dll otoki w czasie wykonywania.Istnieje więc wiele drobnych szczegółów, które zostały szczegółowo opisane w następującym artykule:
„Beautiful Native Libraries (in Python) ”: http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/
źródło
Istnieje również jedna możliwość użycia GObject Introspection dla bibliotek korzystających z GLib .
źródło
Wiem, że to stare pytanie, ale ta sprawa pojawia się w Google, gdy wyszukujesz takie rzeczy
ctypes vs cython
, a większość odpowiedzi tutaj jest napisana przez tych, którzy są biegli jużcython
lubc
którzy mogą nie odzwierciedlać faktycznego czasu, jaki potrzebujesz zainwestować, aby je poznać wdrożyć swoje rozwiązanie. Jestem kompletnym początkującym w obu. Nigdy wcześniej nie dotykałemcython
i mam bardzo małe doświadczeniec/c++
.Przez ostatnie dwa dni szukałem sposobu na przeniesienie dużej części kodu pod kątem wydajności na coś niższego niż Python. Zaimplementowałem mój kod zarówno w, jak
ctypes
iCython
, który składał się zasadniczo z dwóch prostych funkcji.Miałem ogromną listę ciągów do przetworzenia. Zawiadomienie
list
istring
. Oba typy nie odpowiadają idealnie typomc
, ponieważ ciągi Pythona są domyślnie Unicode, ac
ciągi nie są. Listy w pythonie po prostu NIE są tablicami c.Oto mój werdykt. Zastosowanie
cython
. Bardziej płynnie integruje się z Pythonem i ogólnie ułatwia pracę. Gdy coś pójdzie nie tak,ctypes
po prostu rzuca ci segfault, przynajmniejcython
da ci kompilowane ostrzeżenia ze śledzeniem stosu, gdy tylko jest to możliwe, i możesz łatwo zwrócić prawidłowy obiekt pythoncython
.Oto szczegółowe informacje o tym, ile czasu potrzebowałem na zainwestowanie w nich obu, aby wdrożyć tę samą funkcję. Nawiasem mówiąc, zrobiłem bardzo mało programowania w C / C ++:
Typy:
c
kod działa.c
kodu.c
kod do rzeczywistej bazy kodu i zobaczyłem, żectypes
nie działa dobrze zmultiprocessing
modułem, ponieważ jego moduł obsługi nie jest domyślnie możliwy do odebrania.multiprocessing
modułu, i ponowiłem próbę.c
kodzie wygenerowała awarie w mojej bazie kodu, mimo że przeszła przez mój kod testowy. Cóż, to prawdopodobnie moja wina, że nie sprawdziłem dobrze z przypadkowymi przypadkami, szukałem szybkiego rozwiązania.c
kodu, a przy drugiej lub trzeciej iteracji używającej go pętli pytona miałem problem zUnicodeError
nieszyfrowaniem bajtu w pewnej pozycji, chociaż kodowałem i dekodowałem wszystko wyraźnie.W tym momencie postanowiłem poszukać alternatywy i postanowiłem sprawdzić
cython
:setuptools
zamiastdistutils
.setup.py
celu użycia skompilowanego modułu w mojej bazie kodu.multiprocessing
wersji bazy kodu. To działa.Dla przypomnienia, oczywiście nie mierzyłem dokładnego czasu mojej inwestycji. Może się zdarzyć, że mój sposób postrzegania czasu był nieco uważny z powodu wysiłku umysłowego wymaganego podczas pracy z typami. Ale to powinno przekazać atmosferę radzenia sobie z
cython
ictypes
źródło