Câu trả lời:
Bạn nên xem Boost.Python . Đây là phần giới thiệu ngắn được lấy từ trang web của họ:
Thư viện Python Python là một khung để giao tiếp với Python và C ++. Nó cho phép bạn hiển thị nhanh chóng và liền mạch các hàm và đối tượng của lớp C ++ cho Python và ngược lại, không sử dụng công cụ đặc biệt nào - chỉ là trình biên dịch C ++ của bạn. Nó được thiết kế để bao bọc các giao diện C ++ không xâm phạm, do đó bạn không cần phải thay đổi mã C ++ để bọc nó, làm cho Boost.Python trở nên lý tưởng để hiển thị các thư viện của bên thứ 3 cho Python. Việc thư viện sử dụng các kỹ thuật siêu lập trình nâng cao giúp đơn giản hóa cú pháp của nó cho người dùng, do đó, mã gói sẽ mang dáng dấp của một loại ngôn ngữ định nghĩa giao diện khai báo (IDL).
ctypes Module này là một phần của thư viện chuẩn, và do đó là ổn định và phổ biến rộng rãi hơn uống một lân , mà luôn luôn có xu hướng để cho tôi vấn đề .
Với ctypes, bạn cần đáp ứng bất kỳ sự phụ thuộc thời gian biên dịch nào vào python và ràng buộc của bạn sẽ hoạt động trên bất kỳ python nào có ctypes, không chỉ là cái mà nó được biên dịch.
Giả sử bạn có một lớp ví dụ C ++ đơn giản mà bạn muốn nói chuyện trong một tệp có tên foo.cpp:
#include <iostream>
class Foo{
public:
void bar(){
std::cout << "Hello" << std::endl;
}
};
Vì ctypes chỉ có thể nói chuyện với các hàm C, nên bạn cần cung cấp cho những người khai báo chúng là "C" bên ngoài
extern "C" {
Foo* Foo_new(){ return new Foo(); }
void Foo_bar(Foo* foo){ foo->bar(); }
}
Tiếp theo, bạn phải biên dịch nó vào một thư viện chia sẻ
g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
Và cuối cùng, bạn phải viết trình bao bọc python của bạn (ví dụ: trong fooWrapper.py)
from ctypes import cdll
lib = cdll.LoadLibrary('./libfoo.so')
class Foo(object):
def __init__(self):
self.obj = lib.Foo_new()
def bar(self):
lib.Foo_bar(self.obj)
Một khi bạn có mà bạn có thể gọi nó như
f = Foo()
f.bar() #and you will see "Hello" on the screen
extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
Foo_delete
hàm và gọi nó từ một hàm hủy python hoặc bọc đối tượng trong một tài nguyên .
Cách nhanh nhất để làm điều này là sử dụng SWIG .
Ví dụ từ hướng dẫn SWIG :
/* File : example.c */
int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);
}
Tệp giao diện:
/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}
extern int fact(int n);
Xây dựng mô-đun Python trên Unix:
swig -python example.i
gcc -fPIC -c example.c example_wrap.c -I/usr/local/include/python2.7
gcc -shared example.o example_wrap.o -o _example.so
Sử dụng:
>>> import example
>>> example.fact(5)
120
Lưu ý rằng bạn phải có python-dev. Ngoài ra, trong một số hệ thống, các tệp tiêu đề python sẽ nằm trong /usr/include/python2.7 dựa trên cách bạn đã cài đặt nó.
Từ hướng dẫn:
SWIG là một trình biên dịch C ++ khá hoàn chỉnh với sự hỗ trợ cho gần như mọi tính năng ngôn ngữ. Điều này bao gồm tiền xử lý, con trỏ, lớp, kế thừa và thậm chí các mẫu C ++. SWIG cũng có thể được sử dụng để đóng gói các cấu trúc và các lớp thành các lớp proxy theo ngôn ngữ đích - phơi bày các chức năng cơ bản theo cách rất tự nhiên.
Tôi bắt đầu hành trình của mình trong liên kết Python <-> C ++ từ trang này, với mục tiêu liên kết các loại dữ liệu cấp cao (vectơ STL đa chiều với danh sách Python) :-)
Đã thử các giải pháp dựa trên cả ctypes và boost.python (và không phải là kỹ sư phần mềm), tôi thấy chúng phức tạp khi yêu cầu liên kết kiểu dữ liệu mức cao, trong khi tôi thấy SWIG đơn giản hơn nhiều cho các trường hợp như vậy.
Do đó, ví dụ này sử dụng SWIG và nó đã được thử nghiệm trong Linux (nhưng SWIG có sẵn và cũng được sử dụng rộng rãi trong Windows).
Mục tiêu là làm cho hàm C ++ có sẵn cho Python lấy ma trận dưới dạng vectơ STL 2D và trả về trung bình của mỗi hàng (dưới dạng vectơ STL 1D).
Mã trong C ++ ("code.cpp") như sau:
#include <vector>
#include "code.h"
using namespace std;
vector<double> average (vector< vector<double> > i_matrix) {
// Compute average of each row..
vector <double> averages;
for (int r = 0; r < i_matrix.size(); r++){
double rsum = 0.0;
double ncols= i_matrix[r].size();
for (int c = 0; c< i_matrix[r].size(); c++){
rsum += i_matrix[r][c];
}
averages.push_back(rsum/ncols);
}
return averages;
}
Tiêu đề tương đương ("code.h") là:
#ifndef _code
#define _code
#include <vector>
std::vector<double> average (std::vector< std::vector<double> > i_matrix);
#endif
Trước tiên chúng tôi biên dịch mã C ++ để tạo tệp đối tượng:
g++ -c -fPIC code.cpp
Sau đó, chúng tôi xác định tệp định nghĩa giao diện SWIG ("code.i") cho các hàm C ++ của chúng tôi.
%module code
%{
#include "code.h"
%}
%include "std_vector.i"
namespace std {
/* On a side note, the names VecDouble and VecVecdouble can be changed, but the order of first the inner vector matters! */
%template(VecDouble) vector<double>;
%template(VecVecdouble) vector< vector<double> >;
}
%include "code.h"
Sử dụng SWIG, chúng tôi tạo mã nguồn giao diện C ++ từ tệp định nghĩa giao diện SWIG ..
swig -c++ -python code.i
Cuối cùng chúng tôi đã biên dịch tệp nguồn giao diện C ++ đã tạo và liên kết mọi thứ lại với nhau để tạo thư viện dùng chung mà Python có thể nhập trực tiếp (vấn đề "_"):
g++ -c -fPIC code_wrap.cxx -I/usr/include/python2.7 -I/usr/lib/python2.7
g++ -shared -Wl,-soname,_code.so -o _code.so code.o code_wrap.o
Bây giờ chúng ta có thể sử dụng hàm trong các tập lệnh Python:
#!/usr/bin/env python
import code
a= [[3,5,7],[8,10,12]]
print a
b = code.average(a)
print "Assignment done"
print a
print b
Ngoài ra pybind11
, nó cũng giống như một phiên bản nhẹ của Boost.Python và tương thích với tất cả các trình biên dịch C ++ hiện đại:
Pytorch
pytorch.org/tutorials/advified/cpp_extension.html Cũng hoạt động đầy đủ trên VS Community
Windows
Kiểm tra pyrex hoặc Cython . Chúng là các ngôn ngữ giống như Python để giao tiếp giữa C / C ++ và Python.
Đối với C ++ hiện đại, hãy sử dụng cppyy: http://cppyy.readthedocs.io/en/latest/
Nó dựa trên Cling, trình thông dịch C ++ cho Clang / LLVM. Các ràng buộc đang trong thời gian chạy và không cần thêm ngôn ngữ trung gian. Nhờ Clang, nó hỗ trợ C ++ 17.
Cài đặt nó bằng pip:
$ pip install cppyy
Đối với các dự án nhỏ, chỉ cần tải thư viện có liên quan và các tiêu đề mà bạn quan tâm. Ví dụ: lấy mã từ ví dụ ctypes là luồng này, nhưng phân chia trong phần tiêu đề và mã:
$ cat foo.h
class Foo {
public:
void bar();
};
$ cat foo.cpp
#include "foo.h"
#include <iostream>
void Foo::bar() { std::cout << "Hello" << std::endl; }
Biên dịch nó:
$ g++ -c -fPIC foo.cpp -o foo.o
$ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
và sử dụng nó:
$ python
>>> import cppyy
>>> cppyy.include("foo.h")
>>> cppyy.load_library("foo")
>>> from cppyy.gbl import Foo
>>> f = Foo()
>>> f.bar()
Hello
>>>
Các dự án lớn được hỗ trợ tự động tải thông tin phản ánh đã chuẩn bị và các đoạn cmake để tạo chúng, để người dùng các gói đã cài đặt có thể chạy đơn giản:
$ python
>>> import cppyy
>>> f = cppyy.gbl.Foo()
>>> f.bar()
Hello
>>>
Nhờ LLVM, các tính năng nâng cao là có thể, chẳng hạn như khởi tạo mẫu tự động. Để tiếp tục ví dụ:
>>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
>>> v.push_back(f)
>>> len(v)
1
>>> v[0].bar()
Hello
>>>
Lưu ý: Tôi là tác giả của cppyy.
swig
, ctypes
hoặc boost.python
. Thay vì bạn phải viết mã để python hoạt động với mã c ++ của bạn ... python thực hiện công việc khó khăn để tìm ra c ++. Giả sử nó thực sự hoạt động.
Bài viết này, tuyên bố Python là tất cả các nhà khoa học cần , về cơ bản nói: Nguyên mẫu đầu tiên mọi thứ trong Python. Sau đó, khi bạn cần tăng tốc một phần, sử dụng SWIG và dịch phần này sang C.
Tôi chưa bao giờ sử dụng nó nhưng tôi đã nghe những điều tốt về ctypes . Nếu bạn đang cố gắng sử dụng nó với C ++, hãy nhớ tránh xáo trộn tên extern "C"
. Cảm ơn vì nhận xét, Florian Bösch.
Tôi nghĩ rằng cffi cho python có thể là một lựa chọn.
Mục tiêu là gọi mã C từ Python. Bạn sẽ có thể làm như vậy mà không cần học ngôn ngữ thứ 3: mọi phương án đều yêu cầu bạn phải học ngôn ngữ của riêng họ (Cython, SWIG) hoặc API (ctypes). Vì vậy, chúng tôi đã cố gắng giả định rằng bạn biết Python và C và giảm thiểu các bit API bổ sung mà bạn cần tìm hiểu.
Câu hỏi là làm thế nào để gọi hàm C từ Python, nếu tôi hiểu đúng. Sau đó, đặt cược tốt nhất là Ctypes (BTW di động trên tất cả các biến thể của Python).
>>> from ctypes import *
>>> libc = cdll.msvcrt
>>> print libc.time(None)
1438069008
>>> printf = libc.printf
>>> printf("Hello, %s\n", "World!")
Hello, World!
14
>>> printf("%d bottles of beer\n", 42)
42 bottles of beer
19
Để được hướng dẫn chi tiết, bạn có thể muốn tham khảo bài viết trên blog của tôi .
Một trong những tài liệu Python chính thức chứa chi tiết về việc mở rộng Python bằng C / C ++ . Ngay cả khi không sử dụng SWIG , nó khá đơn giản và hoạt động hoàn hảo trên Windows.
Cython chắc chắn là con đường để đi, trừ khi bạn dự đoán việc viết các trình bao bọc Java, trong trường hợp đó SWIG có thể thích hợp hơn.
Tôi khuyên bạn nên sử dụng runcython
tiện ích dòng lệnh, nó làm cho quá trình sử dụng Cython cực kỳ dễ dàng. Nếu bạn cần chuyển dữ liệu có cấu trúc sang C ++, hãy xem thư viện protobuf của Google, điều đó rất thuận tiện.
Đây là một ví dụ tối thiểu tôi đã sử dụng cả hai công cụ:
https://github.com/nicodjimenez/python2cpp
Hy vọng nó có thể là một điểm khởi đầu hữu ích.
Trước tiên, bạn nên quyết định mục đích cụ thể của bạn là gì. Tài liệu Python chính thức về mở rộng và nhúng trình thông dịch Python đã được đề cập ở trên, tôi có thể thêm một tổng quan tốt về các phần mở rộng nhị phân . Các trường hợp sử dụng có thể được chia thành 3 loại:
Để đưa ra một số quan điểm rộng hơn cho những người quan tâm khác và vì câu hỏi ban đầu của bạn hơi mơ hồ ("với thư viện C hoặc C ++") tôi nghĩ thông tin này có thể thú vị với bạn. Trên liên kết ở trên, bạn có thể đọc về những nhược điểm của việc sử dụng tiện ích mở rộng nhị phân và các lựa chọn thay thế.
Ngoài các câu trả lời khác được đề xuất, nếu bạn muốn một mô-đun tăng tốc, bạn có thể thử Numba . Nó hoạt động "bằng cách tạo mã máy được tối ưu hóa bằng cơ sở hạ tầng trình biên dịch LLVM tại thời điểm nhập, thời gian chạy hoặc tĩnh (sử dụng công cụ pycc đi kèm)".
Tôi yêu cppyy, nó giúp dễ dàng mở rộng Python bằng mã C ++, tăng đáng kể hiệu năng khi cần.
Nó rất mạnh mẽ và thực sự rất đơn giản để sử dụng,
đây là một ví dụ về cách bạn có thể tạo một mảng numpy và chuyển nó đến một hàm thành viên lớp trong C ++.
cppyy_test.py
import cppyy
import numpy as np
cppyy.include('Buffer.h')
s = cppyy.gbl.Buffer()
numpy_array = np.empty(32000, np.float64)
s.get_numpy_array(numpy_array.data, numpy_array.size)
print(numpy_array[:20])
Bộ đệm.h
struct Buffer {
void get_numpy_array(double *ad, int size) {
for( long i=0; i < size; i++)
ad[i]=i;
}
};
Bạn cũng có thể tạo mô-đun Python rất dễ dàng (với CMake), bằng cách này bạn sẽ tránh biên dịch lại mã C ++ mọi lúc.
pybind11 ví dụ runnable tối thiểu
pybind11 đã được đề cập trước đây tại https://stackoverflow.com/a/38542539/895245 nhưng tôi muốn đưa ra một ví dụ sử dụng cụ thể và một số thảo luận thêm về việc thực hiện.
Tất cả và tất cả, tôi đánh giá cao pybind11 vì nó rất dễ sử dụng: bạn chỉ cần bao gồm một tiêu đề và sau đó pybind11 sử dụng ma thuật mẫu để kiểm tra lớp C ++ mà bạn muốn tiếp xúc với Python và thực hiện điều đó một cách minh bạch.
Nhược điểm của phép thuật mẫu này là nó làm chậm quá trình biên dịch ngay lập tức thêm một vài giây vào bất kỳ tệp nào sử dụng pybind11, xem ví dụ điều tra được thực hiện về vấn đề này . PyTorch đồng ý .
Dưới đây là một ví dụ có thể chạy tối thiểu để cho bạn cảm giác về pybind11 tuyệt vời như thế nào:
class_test.cpp
#include <string>
#include <pybind11/pybind11.h>
struct ClassTest {
ClassTest(const std::string &name) : name(name) { }
void setName(const std::string &name_) { name = name_; }
const std::string &getName() const { return name; }
std::string name;
};
namespace py = pybind11;
PYBIND11_PLUGIN(class_test) {
py::module m("my_module", "pybind11 example plugin");
py::class_<ClassTest>(m, "ClassTest")
.def(py::init<const std::string &>())
.def("setName", &ClassTest::setName)
.def("getName", &ClassTest::getName)
.def_readwrite("name", &ClassTest::name);
return m.ptr();
}
class_test_main.py
#!/usr/bin/env python3
import class_test
my_class_test = class_test.ClassTest("abc");
print(my_class_test.getName())
my_class_test.setName("012")
print(my_class_test.getName())
assert(my_class_test.getName() == my_class_test.name)
Biên dịch và chạy:
#!/usr/bin/env bash
set -eux
g++ `python3-config --cflags` -shared -std=c++11 -fPIC class_test.cpp \
-o class_test`python3-config --extension-suffix` `python3-config --libs`
./class_test_main.py
Ví dụ này cho thấy pybind11 cho phép bạn dễ dàng hiển thị lớp ClassTest
C ++ cho Python! Compilation tạo ra một file có tên class_test.cpython-36m-x86_64-linux-gnu.so
đó class_test_main.py
tự động nhặt như là điểm định nghĩa cho các class_test
mô-đun natively xác định.
Có lẽ việc nhận ra điều này tuyệt vời như thế nào chỉ chìm trong nếu bạn cố gắng làm điều tương tự bằng tay với API Python nguyên bản, xem ví dụ về ví dụ này để làm điều đó, có thêm 10 lần mã: https://github.com /cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c Trên ví dụ đó, bạn có thể thấy mã C phải xác định một cách đau đớn và rõ ràng như thế nào về mã Python. metadata...). Xem thêm:
pybind11 tuyên bố là tương tự như Boost.Python
đã được đề cập tại https://stackoverflow.com/a/145436/895245 nhưng tối thiểu hơn vì nó được giải phóng khỏi sự phình to của bên trong dự án Boost:
pybind11 là một thư viện chỉ dành cho tiêu đề nhẹ, hiển thị các loại C ++ trong Python và ngược lại, chủ yếu để tạo các ràng buộc Python của mã C ++ hiện có. Mục tiêu và cú pháp của nó tương tự như thư viện Boost.Python tuyệt vời của David Abrahams: để giảm thiểu mã soạn sẵn trong các mô-đun mở rộng truyền thống bằng cách suy ra thông tin loại bằng cách sử dụng nội suy thời gian biên dịch.
Vấn đề chính với Boost.Python, và lý do để tạo ra một dự án tương tự như vậy là Boost. Boost là một bộ thư viện tiện ích rất lớn và phức tạp, hoạt động với hầu hết mọi trình biên dịch C ++ đang tồn tại. Khả năng tương thích này có chi phí của nó: các thủ thuật và giải pháp mẫu phức tạp là cần thiết để hỗ trợ các mẫu biên dịch lâu đời nhất và khó tính nhất. Giờ đây, trình biên dịch tương thích C ++ 11 đã có mặt rộng rãi, bộ máy hạng nặng này đã trở thành một sự phụ thuộc quá lớn và không cần thiết.
Hãy nghĩ về thư viện này như một phiên bản nhỏ của Boost.Python với mọi thứ bị loại bỏ không liên quan đến việc tạo ràng buộc. Không có nhận xét, các tệp tiêu đề lõi chỉ yêu cầu ~ dòng mã 4K và phụ thuộc vào Python (2.7 hoặc 3.x hoặc PyPy2.7> = 5.7) và thư viện chuẩn C ++. Việc triển khai nhỏ gọn này có thể thực hiện được nhờ một số tính năng ngôn ngữ mới của C ++ 11 (cụ thể: bộ dữ liệu, chức năng lambda và các mẫu biến đổi). Kể từ khi thành lập, thư viện này đã phát triển vượt xa Boost.Python theo nhiều cách, dẫn đến mã ràng buộc đơn giản hơn đáng kể trong nhiều tình huống phổ biến.
pybind11 cũng là sự thay thế không phải bản địa duy nhất được làm sáng tỏ bởi tài liệu ràng buộc Microsoft Python C hiện tại tại: https://docs.microsoft.com/en-us/visualstudio/python/usiness-with-c-cpp-python-in- visual-studio? view = vs-2019 ( lưu trữ ).
Đã thử nghiệm trên Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0.