โทรหา C / C ++ จาก Python หรือไม่


521

อะไรจะเป็นวิธีที่เร็วที่สุดในการสร้าง Python ที่เชื่อมโยงกับไลบรารี C หรือ C ++

(ฉันใช้ Windows หากเรื่องนี้)

คำตอบ:


170

คุณควรมีลักษณะที่Boost.Python นี่คือคำแนะนำสั้น ๆ ที่นำมาจากเว็บไซต์ของพวกเขา:

Boost Python Library เป็นเฟรมเวิร์กสำหรับเชื่อมต่อ Python และ C ++ ช่วยให้คุณสามารถเปิดเผยฟังก์ชันและวัตถุของคลาส C ++ ได้อย่างรวดเร็วและต่อเนื่องไปยัง Python และในทางกลับกันโดยไม่ต้องใช้เครื่องมือพิเศษ - เพียงแค่คอมไพเลอร์ C ++ ของคุณ มันถูกออกแบบมาเพื่อรวมส่วนต่อประสาน C ++ เข้าด้วยกันโดยไม่เป็นการรุกล้ำดังนั้นคุณไม่ควรเปลี่ยนรหัส C ++ เลยเพื่อห่อหุ้มทำให้ Boost.Python เหมาะสำหรับการเปิดเผยไลบรารีของบุคคลที่สามเป็น Python การใช้เทคนิค metaprogramming ขั้นสูงของห้องสมุดทำให้ผู้ใช้เข้าใจง่ายยิ่งขึ้นดังนั้นรหัสการตัดจะมีลักษณะของภาษาที่นิยามอินเตอร์เฟสแบบอินเทอร์เฟซ (IDL)


Boost.Python เป็นหนึ่งในห้องสมุดที่เป็นมิตรต่อผู้ใช้มากขึ้นใน Boost สำหรับการเรียกฟังก์ชั่น API ที่เรียบง่ายมันค่อนข้างตรงไปตรงมาและให้ข้อมูลที่คุณต้องเขียนเอง มันซับซ้อนกว่านี้เล็กน้อยหากคุณต้องการแสดง API เชิงวัตถุ
jwfearn

15
Boost.Python เป็นสิ่งที่เลวร้ายที่สุดเท่าที่จะจินตนาการได้ สำหรับทุกเครื่องใหม่และทุกครั้งที่มีการอัพเกรดจะเกิดปัญหาการเชื่อมโยง
มิลเลอร์

14
เกือบ 11 ปีต่อมาสำหรับการไตร่ตรองเกี่ยวกับคุณภาพของคำตอบนี้
J Evans

4
นี่เป็นวิธีที่ดีที่สุดในการเชื่อมต่อกับไพ ธ อนและ c ++ หรือไม่
tushaR

8
บางทีคุณสามารถลองpybind11ซึ่งมีน้ำหนักเบาเมื่อเทียบกับการเพิ่ม
jdhao

659

ctypesโมดูลเป็นส่วนหนึ่งของห้องสมุดมาตรฐานและดังนั้นจึงมีเสถียรภาพมากขึ้นและสามารถใช้ได้อย่างกว้างขวางกว่าswigซึ่งมีแนวโน้มเสมอที่จะให้ฉันปัญหา

ด้วย ctypes คุณจะต้องตอบสนองการพึ่งพาเวลาในการคอมไพล์ด้วยไพ ธ อนและการผูกของคุณจะทำงานกับไพ ธ อนใด ๆ ที่มี ctypes ไม่ใช่เฉพาะคอมไพล์ที่ถูกคอมไพล์ด้วย

สมมติว่าคุณมีคลาสตัวอย่าง C ++ อย่างง่ายที่คุณต้องการคุยด้วยในไฟล์ชื่อ foo.cpp:

#include <iostream>

class Foo{
    public:
        void bar(){
            std::cout << "Hello" << std::endl;
        }
};

เนื่องจาก ctypes สามารถพูดคุยกับฟังก์ชั่น C ได้เท่านั้นคุณจึงต้องแจ้งให้ผู้ที่ประกาศว่าเป็น "ภายนอก"

extern "C" {
    Foo* Foo_new(){ return new Foo(); }
    void Foo_bar(Foo* foo){ foo->bar(); }
}

ต่อไปคุณจะต้องรวบรวมสิ่งนี้ไปยังห้องสมุดสาธารณะ

g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

และในที่สุดคุณต้องเขียนตัวห่อหลาม (เช่นใน 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)

เมื่อคุณมีสิ่งที่คุณสามารถโทรได้เช่น

f = Foo()
f.bar() #and you will see "Hello" on the screen

14
นี่เป็นสิ่งที่ boost.python ทำเพื่อคุณในการเรียกใช้ฟังก์ชั่นเดียว
Martin Beckett

203
ctypes อยู่ในไลบรารี่มาตรฐานของไพ ธ อนคำว่า swig และ boost ไม่ใช่ Swig และ boost ใช้โมดูลส่วนขยายดังนั้นจึงเชื่อมโยงกับ python รุ่นรองซึ่งเป็นออบเจ็กต์ที่ใช้ร่วมกันโดยอิสระ การสร้างชุดคลุมแบบ swig หรือ boost wraps สามารถทำให้เกิดความเจ็บปวดได้ ctypes ไม่สร้างข้อกำหนด
Florian Bösch

25
บูสต์เพิ่มขึ้นอาศัยเวทมนต์ของวูดูและระบบสร้างที่กำหนดเองทั้งหมด ctypes อาศัยความเรียบง่าย ctypes เป็นแบบไดนามิก boost เป็นแบบคงที่ ctypes สามารถจัดการไลบรารี่รุ่นต่าง ๆ ได้ เพิ่มไม่สามารถ
Florian Bösch

32
บน Windows ฉันต้องระบุ __declspec (dllexport) ในลายเซ็นการทำงานของฉันเพื่อให้ Python มองเห็นได้ จากตัวอย่างข้างต้นสิ่งนี้จะสอดคล้องกับ: extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
Alan Macdonald

13
อย่าลืมที่จะลบตัวชี้หลังจากนั้นเช่นให้Foo_deleteฟังก์ชั่นและเรียกมันว่าอย่างใดอย่างหนึ่งจาก destructor หลามหรือห่อวัตถุในทรัพยากร
Adversus

58

วิธีที่เร็วที่สุดที่จะทำเช่นนี้คือการใช้SWIG

ตัวอย่างจากการกวดวิชา SWIG :

/* File : example.c */
int fact(int n) {
    if (n <= 1) return 1;
    else return n*fact(n-1);
}

ไฟล์ส่วนต่อประสาน:

/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}

extern int fact(int n);

การสร้างโมดูล Python บน 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

การใช้งาน:

>>> import example
>>> example.fact(5)
120

โปรดทราบว่าคุณต้องมี python-dev นอกจากนี้ในบางระบบไฟล์ส่วนหัวของไพ ธ อนจะอยู่ใน /usr/include/python2.7 ตามวิธีที่คุณติดตั้ง

จากบทช่วยสอน:

SWIG เป็นคอมไพเลอร์ C ++ ที่ค่อนข้างสมบูรณ์พร้อมรองรับคุณสมบัติเกือบทุกภาษา ซึ่งรวมถึงการดำเนินการล่วงหน้าพอยน์เตอร์คลาสการสืบทอดและแม้แต่เทมเพลต C ++ SWIG ยังสามารถใช้กับโครงสร้างแพ็กเกจและคลาสเป็นคลาสพร็อกซีในภาษาเป้าหมาย - เปิดเผยฟังก์ชันการทำงานพื้นฐานในลักษณะที่เป็นธรรมชาติ


50

ฉันเริ่มการเดินทางของฉันใน Python <-> C ++ ที่มีผลผูกพันจากหน้านี้โดยมีวัตถุประสงค์เพื่อเชื่อมโยงชนิดข้อมูลระดับสูง (พหุมิติ STL หลายมิติกับรายการ Python) :-)

ต้องลองวิธีแก้ปัญหาตามทั้งctypesและboost.python (และไม่ใช่วิศวกรซอฟต์แวร์) ฉันพบว่ามันซับซ้อนเมื่อจำเป็นต้องใช้การเชื่อมข้อมูลระดับสูงในขณะที่ฉันได้พบว่าSWIGง่ายกว่ามากสำหรับกรณีดังกล่าว

ตัวอย่างนี้ใช้ SWIG และได้รับการทดสอบใน Linux (แต่ SWIG นั้นมีให้ใช้และใช้กันอย่างแพร่หลายใน Windows ด้วย)

วัตถุประสงค์คือเพื่อให้ฟังก์ชัน C ++ พร้อมใช้งานสำหรับ Python ที่ใช้เมทริกซ์ในรูปแบบของเวกเตอร์ 2D STL และส่งกลับค่าเฉลี่ยของแต่ละแถว (เป็นเวกเตอร์ STL 1D)

รหัสใน C ++ ("code.cpp") มีดังต่อไปนี้:

#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;
}

ส่วนหัวที่เทียบเท่า ("code.h") คือ:

#ifndef _code
#define _code

#include <vector>

std::vector<double> average (std::vector< std::vector<double> > i_matrix);

#endif

ก่อนอื่นเรารวบรวมรหัส C ++ เพื่อสร้างวัตถุ

g++ -c -fPIC code.cpp

จากนั้นเราจะกำหนดไฟล์นิยามอินเตอร์เฟส SWIG ("code.i") สำหรับฟังก์ชั่น C ++ ของเรา

%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"

การใช้ SWIG เราจะสร้างซอร์สโค้ดอินเตอร์เฟส C ++ จากไฟล์นิยามอินเตอร์เฟส SWIG ..

swig -c++ -python code.i

ในที่สุดเราก็รวบรวมไฟล์ซอร์สอินเตอร์เฟส C ++ ที่สร้างขึ้นและเชื่อมโยงทุกอย่างเข้าด้วยกันเพื่อสร้างไลบรารี่ที่แชร์ซึ่ง Python สามารถนำเข้าได้โดยตรง (เรื่อง "_"):

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

ตอนนี้เราสามารถใช้ฟังก์ชันในสคริปต์ 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

การติดตั้งเคสจริงซึ่งในเวกเตอร์รหัส C ++ นั้นถูกส่งผ่านเป็นการอ้างอิงแบบไม่อ้างอิงและพร้อมใช้งานโดยไพ ธ อนเป็นพารามิเตอร์เอาต์พุต: lobianco.org/antonello/personal:portfolio:portopt
Antonello

33

นอกจากนี้ยังมีpybind11ซึ่งคล้ายกับBoost.Pythonรุ่นน้ำหนักเบาและเข้ากันได้กับคอมไพเลอร์ C ++ ที่ทันสมัยทั้งหมด:

https://pybind11.readthedocs.io/en/latest/


1
วันนี้ !! 2020นี่คือคำตอบที่ดีที่สุด! เป็นไลบรารีส่วนหัวเทมเพลตเท่านั้น โครงการขนาดใหญ่จำนวนมากที่เกี่ยวข้องแนะนำเช่นPytorch pytorch.org/tutorials/advanced/cpp_extension.htmlทำงานอย่างสมบูรณ์บนVS CommunityWindows
eusoubrasileiro

30

ตรวจสอบPyrexหรือCython เป็นภาษาที่เหมือนกับ Python สำหรับการเชื่อมต่อระหว่าง C / C ++ และ Python


1
+1 สำหรับ Cython! ฉันไม่ได้ลอง cffi ดังนั้นฉันจึงไม่สามารถพูดได้ว่าดีกว่า แต่ฉันมีประสบการณ์ที่ดีกับ Cython - คุณยังคงเขียนรหัส Python แต่คุณสามารถใช้ C ในนั้นได้ มันค่อนข้างยากสำหรับฉันที่จะตั้งค่ากระบวนการสร้างด้วย Cython ซึ่งฉันอธิบายในภายหลังในบล็อกโพสต์: martinsosic.com/development/2016/02/08/…
Martinsos

คุณอาจต้องการปรับปรุงคำตอบเพื่อไม่ให้เป็นคำตอบแบบลิงก์เท่านั้นอีกต่อไป
Adelin

ฉันใช้ Cython มาประมาณหนึ่งสัปดาห์และฉันชอบมันมาก: 1) ฉันเห็น ctypes ที่ใช้งานอยู่และเป็น UGLY และเกิดข้อผิดพลาดได้ง่ายมากกับข้อผิดพลาดมากมาย 2) ช่วยให้คุณสามารถใช้รหัส Python และเร่งความเร็วได้ จากการพิมพ์สิ่งเดียวแบบคงที่ 3) มันเป็นเรื่องง่ายที่จะเขียนตัวห่องูหลามสำหรับวิธี C / C ++ และวัตถุ 4) มันยังได้รับการสนับสนุนอย่างดี มันสามารถทำได้ด้วยคำแนะนำเพิ่มเติมการติดตั้งแบบเผชิญหน้ากับ venvs และการคอมไพล์ข้ามซึ่งใช้เวลาเล็กน้อยในการทำงาน มีวิดีโอสอนที่ดีมาก 4 ชั่วโมงที่นี่: youtube.com/watch?v=gMvkiQ-gOW8
Den-Jason

22

สำหรับ C ++ สมัยใหม่ให้ใช้ cppyy: http://cppyy.readthedocs.io/en/latest/

มันขึ้นอยู่กับ Cling ล่าม C ++ สำหรับ Clang / LLVM การโยงเป็นเวลาทำงานและไม่จำเป็นต้องใช้ภาษากลางเพิ่มเติม ต้องขอบคุณ Clang มันรองรับ C ++ 17

ติดตั้งโดยใช้ pip:

    $ pip install cppyy

สำหรับโครงการขนาดเล็กเพียงโหลดห้องสมุดที่เกี่ยวข้องและส่วนหัวที่คุณสนใจเช่นใช้รหัสจากตัวอย่าง ctypes คือหัวข้อนี้ แต่แยกในส่วนหัวและส่วนรหัส:

    $ cat foo.h
    class Foo {
    public:
        void bar();
    };

    $ cat foo.cpp
    #include "foo.h"
    #include <iostream>

    void Foo::bar() { std::cout << "Hello" << std::endl; }

รวบรวมมัน

    $ g++ -c -fPIC foo.cpp -o foo.o
    $ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

และใช้มัน:

    $ python
    >>> import cppyy
    >>> cppyy.include("foo.h")
    >>> cppyy.load_library("foo")
    >>> from cppyy.gbl import Foo
    >>> f = Foo()
    >>> f.bar()
    Hello
    >>>

โครงการขนาดใหญ่ได้รับการสนับสนุนด้วยการโหลดข้อมูลการสะท้อนที่เตรียมไว้โดยอัตโนมัติและชิ้นส่วน cmake เพื่อสร้างพวกเขาเพื่อให้ผู้ใช้งานแพคเกจที่ติดตั้งสามารถเรียกใช้:

    $ python
    >>> import cppyy
    >>> f = cppyy.gbl.Foo()
    >>> f.bar()
    Hello
    >>>

ต้องขอบคุณ LLVM คุณสามารถใช้คุณสมบัติขั้นสูงเช่นการสร้างแม่แบบอัตโนมัติ หากต้องการทำตัวอย่างต่อไป:

    >>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
    >>> v.push_back(f)
    >>> len(v)
    1
    >>> v[0].bar()
    Hello
    >>>

หมายเหตุ: ฉันเป็นผู้เขียน cppyy


3
ไม่จริง: Cython เป็นภาษาการเขียนโปรแกรมเหมือนกับ Python ในการเขียนโมดูลส่วนขยาย C สำหรับ Python (โค้ด Cython ได้รับการแปลเป็น C พร้อมกับ C-API สำเร็จรูปที่จำเป็น) มันให้การสนับสนุน C ++ พื้นฐานบางอย่าง การเขียนโปรแกรมด้วย cppyy เกี่ยวข้องกับ Python และ C ++ เท่านั้นไม่มีส่วนขยายภาษา มันทำงานเต็มเวลาและไม่สร้างรหัสออฟไลน์ (รุ่นคนขี้เกียจปรับขนาดได้ดีกว่ามาก) ตั้งเป้าหมาย C ++ ที่ทันสมัย ​​(รวมถึงอินเทอร์เฟซอัตโนมัติเทมเพลตการย้าย initializer_lists แลมบ์ดา ฯลฯ ฯลฯ ) และสนับสนุน PyPy (เช่นไม่ใช่ผ่านเลเยอร์การจำลอง C-API ช้า)
Wim Lavrijsen

2
นี้กระดาษ PyHPC'16มีช่วงของตัวเลขมาตรฐาน อย่างไรก็ตามตั้งแต่นั้นเป็นต้นมาก็ได้มีการปรับปรุงด้าน CPython อย่างชัดเจน
Wim Lavrijsen

ผมชอบวิธีนี้เพราะคุณไม่ต้องทำงานบูรณาการเพิ่มเติมกับswig, หรือctypes boost.pythonแทนที่จะต้องเขียนโค้ดเพื่อให้ python ทำงานกับ c ++ code ของคุณ ... python ทำงานอย่างหนักเพื่อหา c ++ สมมติว่ามันใช้งานได้จริง
Trevor Boyd Smith

cppyy น่าสนใจมาก! ฉันเห็นในเอกสารที่มีการแจกจ่ายซ้ำและการบรรจุล่วงหน้า เป็นที่รู้จักกันดีว่าทำงานได้ดีกับเครื่องมือที่ใช้รหัสไพ ธ อนแพ็คเกจเช่น (PyInstaller) และสิ่งนี้เกี่ยวข้องกับโครงการ ROOT หรือใช้ประโยชน์จากงานของมันหรือไม่?
JimB

ขอบคุณ! ฉันไม่คุ้นเคยกับ PyInstaller แต่ "พจนานุกรม" ที่รวมค่าการประกาศล่วงหน้าพา ธ และส่วนหัวเป็นรหัส C ++ ที่คอมไพล์กับ libs ที่ใช้ร่วมกัน เนื่องจาก cppyy ถูกใช้เพื่อผูกรหัส C ++ ฉันเข้าใจว่าการจัดการรหัส C ++ เพิ่มเติมควรจะดี และรหัสนั้นไม่ได้ขึ้นอยู่กับ Python C-API (เฉพาะโมดูล libcppyy เท่านั้น) ซึ่งจะทำให้สิ่งต่าง ๆ ง่ายขึ้น cppyy นั้นสามารถติดตั้งได้จาก conda-forge หรือ pypi (pip) ดังนั้นสภาพแวดล้อมเหล่านี้จึงใช้ได้จริง ใช่ cppyy เริ่มจากทางแยกจาก PyROOT แต่มันก็พัฒนาขึ้นมากจนทีม ROOT กำลังรีบูท PyROOT ด้านบนของ cppyy
Wim Lavrijsen

20

บทความนี้อ้างว่า Python เป็นความต้องการของนักวิทยาศาสตร์โดยทั่วไปบอกว่า: ต้นแบบแรกทุกอย่างใน Python จากนั้นเมื่อคุณต้องการเร่งความเร็วส่วนให้ใช้ SWIG และแปลส่วนนี้เป็น C


15

ฉันไม่เคยใช้มัน แต่ผมเคยได้ยินสิ่งที่ดีเกี่ยวctypes ถ้าคุณกำลังพยายามที่จะใช้มันกับ C ++ ให้แน่ใจว่าชื่อ mangling extern "C"หลบเลี่ยงผ่าน ขอบคุณสำหรับความคิดเห็น Florian Bösch


13

ฉันคิดว่า cffi สำหรับ python สามารถเป็นตัวเลือกได้

เป้าหมายคือโทรหารหัส C จาก Python คุณควรจะสามารถทำได้โดยไม่ต้องเรียนรู้ภาษาที่ 3: ทุก ๆ ทางเลือกต้องการให้คุณเรียนรู้ภาษาของตนเอง (Cython, SWIG) หรือ API (ctypes) ดังนั้นเราจึงลองสมมติว่าคุณรู้จัก Python และ C และย่อส่วนเล็กน้อยของ API ที่คุณต้องเรียนรู้

http://cffi.readthedocs.org/en/release-0.7/


2
ฉันคิดว่าสิ่งนี้เรียกได้เพียง c (ไม่ใช่ c ++) ยังคงเป็น +1 (ฉันชอบ cffi)
Andy Hayden

8

คำถามคือวิธีการเรียกใช้ฟังก์ชัน C จาก Python ถ้าฉันเข้าใจถูกต้อง ทางออกที่ดีที่สุดคือ Ctypes (BTW แบบพกพาสำหรับทุกรุ่นของ 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

สำหรับคำแนะนำรายละเอียดของคุณอาจต้องการที่จะอ้างถึงบทความบล็อกของฉัน


อาจเป็นเรื่องที่น่าสังเกตว่าในขณะที่ ctypes นั้นพกพาได้รหัสของคุณต้องใช้ไลบรารี C เฉพาะ Windows
Palec


6

Cython เป็นวิธีที่แน่นอนที่จะไปเว้นแต่คุณคาดว่าจะมีการเขียน wraps Java ซึ่งในกรณีนี้อาจจะดีกว่า SWIG

ฉันแนะนำให้ใช้runcythonอรรถประโยชน์บรรทัดคำสั่งมันทำให้กระบวนการใช้ Cython ง่ายมาก หากคุณต้องการส่งข้อมูลที่มีโครงสร้างไปยัง C ++ ลองดูที่ห้องสมุด protobuf ของ Google มันสะดวกมาก

นี่คือตัวอย่างเล็กน้อยที่ฉันทำซึ่งใช้ทั้งสองเครื่องมือ

https://github.com/nicodjimenez/python2cpp

หวังว่ามันจะเป็นจุดเริ่มต้นที่มีประโยชน์


5

ก่อนอื่นคุณควรตัดสินใจว่าเป้าหมายของคุณคืออะไร เอกสารงูใหญ่อย่างเป็นทางการในการขยายและการฝังล่ามงูใหญ่ได้กล่าวถึงข้างต้นผมสามารถเพิ่มดีภาพรวมของส่วนขยายไบนารี กรณีการใช้งานสามารถแบ่งออกเป็น 3 ประเภท:

  • โมดูลตัวเร่งความเร็ว : เพื่อให้ทำงานได้เร็วกว่าโค้ด Python ที่เทียบเท่าที่เทียบเท่ากับรันใน CPython
  • โมดูล wrapper : เพื่อแสดงอินเตอร์เฟส C ที่มีอยู่กับโค้ด Python
  • การเข้าถึงระบบระดับต่ำ : เพื่อเข้าถึงคุณลักษณะระดับต่ำของรันไทม์ CPython, ระบบปฏิบัติการหรือฮาร์ดแวร์พื้นฐาน

เพื่อที่จะให้มุมมองที่กว้างขึ้นสำหรับความสนใจอื่น ๆ และเนื่องจากคำถามเริ่มต้นของคุณค่อนข้างคลุมเครือ ("ถึง C หรือ C ++ ไลบรารี่") ฉันคิดว่าข้อมูลนี้อาจเป็นที่สนใจของคุณ ในลิงก์ด้านบนคุณสามารถอ่านข้อเสียของการใช้ส่วนขยายไบนารีและทางเลือกอื่นได้

นอกเหนือจากคำตอบอื่น ๆ ที่แนะนำถ้าคุณต้องการโมดูลคันเร่งคุณสามารถลองNumba ใช้งานได้ "โดยการสร้างรหัสเครื่องที่ดีที่สุดโดยใช้โครงสร้างพื้นฐานคอมไพเลอร์ LLVM ณ เวลานำเข้ารันไทม์หรือแบบคงที่ (ใช้เครื่องมือ pycc ที่รวมอยู่)"


3

ฉันชอบ cppyy ทำให้ง่ายต่อการขยาย Python ด้วยรหัส C ++ เพิ่มประสิทธิภาพอย่างมากเมื่อต้องการ

มันมีประสิทธิภาพและตรงไปตรงมาใช้ง่ายมาก

นี่คือตัวอย่างของวิธีที่คุณสามารถสร้างอาร์เรย์ numpy และส่งผ่านไปยังฟังก์ชันสมาชิกคลาสใน 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])

Buffer.h

struct Buffer {
  void get_numpy_array(double *ad, int size) {
    for( long i=0; i < size; i++)
        ad[i]=i;
  }
};

คุณยังสามารถสร้างโมดูล Python ได้อย่างง่ายดาย (ด้วย CMake) ด้วยวิธีนี้คุณจะหลีกเลี่ยงการคอมไพล์รหัส C ++ ซ้ำได้ตลอดเวลา


2

pybind11 ตัวอย่างน้อยที่สุด runnable

pybind11 ได้รับการกล่าวถึงก่อนหน้านี้ที่https://stackoverflow.com/a/38542539/895245แต่ฉันต้องการยกตัวอย่างการใช้งานที่เป็นรูปธรรมและอภิปรายเพิ่มเติมเกี่ยวกับการใช้งานที่นี่

ทั้งหมดและทั้งหมดฉันขอแนะนำ pybind11 เพราะมันใช้งานง่ายจริง ๆ : คุณเพียงแค่ใส่ส่วนหัวและจากนั้น pybind11 ใช้เทมเพลตเวทย์มนตร์เพื่อตรวจสอบคลาส C ++ ที่คุณต้องการเปิดเผยให้ Python และเห็นได้อย่างชัดเจน

ข้อเสียของเทมเพลตนี้คือการรวบรวมช้าลงทันทีเพิ่มสองสามวินาทีในไฟล์ใด ๆ ที่ใช้ pybind11 ดูตัวอย่างการตรวจสอบที่ทำกับปัญหานี้ PyTorch ตกลง

นี่เป็นตัวอย่างที่ทำงานได้น้อยที่สุดเพื่อให้คุณรู้สึกว่า pybind11 ยอดเยี่ยมคืออะไร:

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)

รวบรวมและเรียกใช้:

#!/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

ตัวอย่างนี้แสดงให้เห็นว่า pybind11 ช่วยให้คุณเปิดเผยClassTestคลาส C ++ กับ Python ได้อย่างง่ายดาย! การคอมไพล์สร้างไฟล์ที่มีชื่อclass_test.cpython-36m-x86_64-linux-gnu.soซึ่งclass_test_main.pyรับโดยอัตโนมัติเป็นจุดนิยามสำหรับclass_testโมดูลที่กำหนดโดยกำเนิด

บางทีการตระหนักถึงความยอดเยี่ยมที่ว่านี้เป็นเพียงการจมถ้าคุณพยายามทำสิ่งเดียวกันด้วยมือกับ Python API ดั้งเดิมดูตัวอย่างของการทำเช่นนี้ซึ่งมีรหัสเพิ่มเติมอีกประมาณ 10x: https://github.com /cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.cในตัวอย่างนั้นคุณสามารถดูว่ารหัส C มีความเจ็บปวดและกำหนดบิตระดับ Python อย่างละเอียดด้วยบิตอย่างไรด้วยข้อมูลทั้งหมดที่มี (สมาชิกวิธีเพิ่มเติม เมตาดาต้า ... ) ดูสิ่งนี้ด้วย:

pybind11 อ้างว่าคล้ายกับBoost.Pythonที่กล่าวถึงในhttps://stackoverflow.com/a/145436/895245แต่น้อยกว่ามากเพราะเป็นอิสระจากการขยายตัวของโครงการ Boost:

pybind11 เป็นไลบราเฉพาะส่วนหัวที่มีการเปิดเผยประเภท C ++ ใน Python และในทางกลับกันส่วนใหญ่เพื่อสร้างการผูก Python ของรหัส C ++ ที่มีอยู่ เป้าหมายและไวยากรณ์ของมันคล้ายกับห้องสมุด Boost.Python ที่ยอดเยี่ยมโดย David Abrahams: เพื่อลดรหัสสำเร็จรูปในโมดูลส่วนขยายแบบดั้งเดิมโดยการอนุมานข้อมูลประเภทโดยใช้การรวบรวมข้อมูลในเวลารวบรวม

ปัญหาหลักของ Boost.Python และสาเหตุของการสร้างโครงการที่คล้ายกันนั้นคือ Boost Boost เป็นชุดของยูทิลิตี้ไลบรารีขนาดใหญ่และซับซ้อนที่ทำงานร่วมกับคอมไพเลอร์ C ++ เกือบทุกตัวที่มีอยู่ ความเข้ากันได้นี้มีค่าใช้จ่าย: เทคนิคเทมเพลต arcane และวิธีแก้ไขปัญหาเป็นสิ่งจำเป็นเพื่อสนับสนุนตัวอย่างคอมไพเลอร์ที่เก่าแก่ที่สุด ตอนนี้คอมไพเลอร์ที่เข้ากันได้กับ C ++ 11 มีวางจำหน่ายอย่างกว้างขวางแล้วเครื่องจักรกลหนักนี้จึงกลายเป็นสิ่งที่ต้องพึ่งพามากและไม่จำเป็น

คิดว่าไลบรารี่นี้เป็น Boost.Python เวอร์ชันที่มีอยู่ในตัวเองพร้อมกับทุกอย่างที่ถูกถอดออกซึ่งไม่เกี่ยวข้องกับการสร้างผูกพัน หากไม่มีความคิดเห็นไฟล์ส่วนหัวหลักต้องการเพียงบรรทัดของโค้ด ~ 4K และขึ้นอยู่กับ Python (2.7 หรือ 3.x หรือ PyPy2.7> = 5.7) และไลบรารีมาตรฐาน C ++ การใช้งานขนาดกะทัดรัดนี้เป็นไปได้ด้วยคุณสมบัติภาษา C ++ 11 ใหม่บางอย่าง (โดยเฉพาะ: tuples, ฟังก์ชั่นแลมบ์ดาและเท็มเพลตแบบแปรผัน) นับตั้งแต่การสร้างไลบรารีนี้ได้เติบโตเกินกว่า Boost.Python ในหลาย ๆ ด้านนำไปสู่การผูกรหัสที่ง่ายขึ้นอย่างมากในสถานการณ์ทั่วไปมากมาย

pybind11 เป็นทางเลือกที่ไม่ใช่เจ้าของภาษาเท่านั้นโดยเอกสารประกอบการเชื่อมต่อ Microsoft Python C ปัจจุบันที่: https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in- visual-studio? view = vs-2019 ( ไฟล์เก็บถาวร )

ทดสอบกับ Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.