2017-12-01 115 views
5

Próbuję spakować istniejący kod Pythona i nowy kod C++ 11 za pomocą CMake i Pybind 11. Myślę, że brakuje mi czegoś prostego do dodania do skryptów CMake, ale nie mogę go nigdzie znaleźć: przykłady pybind11 mają tylko C++ Kod i żaden z Pythona, inne zasoby online są raczej zawiłe i nieaktualne - więc nie mogę pojąć, jak spakować funkcje w obu językach i udostępnić je za pośrednictwem Pythona import my_package w dół ... jako przykład, nie sklonowali cmake_example z pybind11 i added a mult function do cmake_example/mult.pypybind11: jak spakować kod C++ i python do pojedynczej paczki?

def mult(a, b): 
    return a * b 

jak byłoby uczynić widocznym także add i subtract przejść t est poniżej?

import cmake_example as m 

assert m.__version__ == '0.0.1' 
assert m.add(1, 2) == 3 
assert m.subtract(1, 2) == -1 
assert m.mult(2, 2) == 4 

Obecnie test ten fails..

Dzięki!

Odpowiedz

3

Najprostsze rozwiązanie ma nic zrób z pybind11 jako takie. To, co zwykle robią autorzy, gdy chcą łączyć czyste rozszerzenia Python i C/Cython/inne natywne w tym samym pakiecie, jest następujące.

Tworzysz dwa moduły.

  1. mymodule to interfejs publiczny, czysta moduł Pythona
  2. _mymodule jest prywatnym implementacji, spełnione moduł

Następnie w mymodule importować niezbędne symbole z _mymoudle (i awaryjne do czystej wersji Pythona Jeśli to konieczne).

Oto przykład z yarl opakowania:

  1. quoting.py

    try: 
        from ._quoting import _quote, _unquote 
        quote = _quote 
        unquote = _unquote 
    except ImportError: # pragma: no cover 
        quote = _py_quote 
        unquote = _py_unquote 
    
  2. _quoting.pyx

Aktualizacji

Oto skrypt. Ze względu na powtarzalność robię to przeciwko oryginałowi cmake_example.

git clone --recursive https://github.com/pybind/cmake_example.git 
# at the time of writing https://github.com/pybind/cmake_example/commit/8818f493 
cd cmake_example 

Teraz stwórz czyste moduły Pythona (wewnątrz cmake_example/cmake_example).

cmake_example/__init__.py

"""Root module of your package""" 

cmake_example/math.py

def mul(a, b): 
    """Pure Python-only function""" 
    return a * b 


def add(a, b): 
    """Fallback function"""  
    return a + b  

try: 
    from ._math import add 
except ImportError: 
    pass 

Teraz zmodyfikować istniejące pliki, aby włączyć moduł cmake_example w cmake_example._math.

src/main.cpp (subtract usunięte dla zwięzłość)

#include <pybind11/pybind11.h> 

int add(int i, int j) { 
    return i + j; 
} 

namespace py = pybind11; 

PYBIND11_MODULE(_math, m) { 
    m.doc() = R"pbdoc(
     Pybind11 example plugin 
     ----------------------- 

     .. currentmodule:: _math 

     .. autosummary:: 
      :toctree: _generate 

      add 
    )pbdoc"; 

    m.def("add", &add, R"pbdoc(
     Add two numbers 

     Some other explanation about the add function. 
    )pbdoc"); 

#ifdef VERSION_INFO 
    m.attr("__version__") = VERSION_INFO; 
#else 
    m.attr("__version__") = "dev"; 
#endif 
} 

CMakeLists.txt

cmake_minimum_required(VERSION 2.8.12) 
project(cmake_example) 

add_subdirectory(pybind11) 
pybind11_add_module(_math src/main.cpp) 

setup.py

# the above stays intact 

from subprocess import CalledProcessError 

kwargs = dict(
    name='cmake_example', 
    version='0.0.1', 
    author='Dean Moldovan', 
    author_email='[email protected]', 
    description='A test project using pybind11 and CMake', 
    long_description='', 
    ext_modules=[CMakeExtension('cmake_example._math')], 
    cmdclass=dict(build_ext=CMakeBuild), 
    zip_safe=False, 
    packages=['cmake_example'] 
) 

# likely there are more exceptions, take a look at yarl example 
try: 
    setup(**kwargs)   
except CalledProcessError: 
    print('Failed to build extension!') 
    del kwargs['ext_modules'] 
    setup(**kwargs) 

Teraz możemy go zbudować.

python setup.py bdist_wheel 

W moim przypadku to produkuje dist/cmake_example-0.0.1-cp27-cp27mu-linux_x86_64.whl (jeśli C++ kompilacja nie jest cmake_example-0.0.1-py2-none-any.whl). Oto, co jej treść (unzip -l ...):

Archive: cmake_example-0.0.1-cp27-cp27mu-linux_x86_64.whl 
    Length  Date Time Name 
--------- ---------- ----- ---- 
     0 2017-12-05 21:42 cmake_example/__init__.py 
    81088 2017-12-05 21:43 cmake_example/_math.so 
     223 2017-12-05 21:46 cmake_example/math.py 
     10 2017-12-05 21:48 cmake_example-0.0.1.dist-info/DESCRIPTION.rst 
     343 2017-12-05 21:48 cmake_example-0.0.1.dist-info/metadata.json 
     14 2017-12-05 21:48 cmake_example-0.0.1.dist-info/top_level.txt 
     105 2017-12-05 21:48 cmake_example-0.0.1.dist-info/WHEEL 
     226 2017-12-05 21:48 cmake_example-0.0.1.dist-info/METADATA 
     766 2017-12-05 21:48 cmake_example-0.0.1.dist-info/RECORD 
---------      ------- 
    82775      9 files 
+0

dziękuję, jest to bardzo przydatna sugestia. Nie jestem wystarczająco biegły w pythonie i jego pakiecie narzędzi do pakowania, aby wdrożyć proponowane rozwiązanie na własną rękę, nawet w przypadku tego prostego przykładu umieszczam na githubie.czy byłbyś w stanie mi pomóc, kształtując go tak, aby miał "mult" w języku C++ i awarię w pytonie, jak zasugerowałeś? więc mogę go wykorzystać jako punkt wyjścia do budowy i dystrybucji mojego własnego pakietu? Dziękuję Ci! – seninp

+0

@seninp Spójrz. – saaj

+0

Bardzo podoba mi się sposób, w jaki to działa, zachowując separację między źródłami C++ i Python oraz możliwość testowania/używania tych niezależnie. Również buduje i testuje seamlesly na Travis i codecov jest w stanie wyciągnąć wskaźniki zasięgu. Przyjmowanie odpowiedzi - jeszcze raz dziękuję! – seninp

2

Po sklonowany repo, CD do najwyższego poziomu katalogu `cmake_example”

Zmień ./src/main.cpp włączenia funkcji "mult":

#include <pybind11/pybind11.h> 

int add(int i, int j) { 
    return i + j; 
} 

int mult(int i, int j) { 
    return i * j; 
} 

namespace py = pybind11; 

PYBIND11_MODULE(cmake_example, m) { 
    m.doc() = R"pbdoc(
     Pybind11 example plugin 
     ----------------------- 

     .. currentmodule:: cmake_example 

     .. autosummary:: 
      :toctree: _generate 

      add 
      subtract 
      mult 

    )pbdoc"; 

    m.def("add", &add, R"pbdoc(
     Add two numbers 

     Some other explanation about the add function. 
    )pbdoc"); 

    m.def("mult", &mult, R"pbdoc(
     Multiply two numbers 

     Some other explanation about the mult function. 
    )pbdoc"); 

(reszta pliku jest taka sama)

teraz zrobić to:

$ cmake -H. -Bbuild 
$ cmake --build build -- -j3 

moduł do importu zostanie CRE w katalogu ./build. Przejdź do niego, a następnie w powłoce Pythona twój przykład powinien działać.

Przy imporcie przestrzeni nazw, można zrobić coś z pkgutil:

utworzyć strukturę katalogów:

./my_mod 
    __init__.py 
    cmake_example.***.so 

i inną strukturę równoległą

./extensions 
    /my_mod 
     __init__.py 
     cmake_example_py.py 

i miejsce w ./my_mod/__init__.py

import pkgutil 
__path__ = pkgutil.extend_path(__path__, __name__) 

from .cmake_example import add, subtract 
from .cmake_example_py import mult 

w ./extensions/my_mod/__init__.py

from cmake_example_py import mult 

Następnie dołączyć zarówno ./my_mod i ./extensions/my_mod do swojej $ PYTHONPATH, to po prostu może działać (to robi w moim przykładzie)

+0

ten byłby poprawny odpowiedź, jeśli trzeba kodować 'mult' w języku C, ale trzeba go mieć w Pythonie i pakowane przy użyciu cmake wraz z' add' i "odejmij" napisane w C ... dziękuję – seninp

+0

Dodano coś, co adresuje to za pomocą 'pkgutil'. Jakoś pominąłem tę część w moim oryginalnym wpisie. –