Python 綁定:從 Python 調用 C 或 C++ |【生長吧!Python!】 【生長吧!Python】有獎征文火熱進行中:https://bbs.huaweicloud.com/blogs/278897

      網友投稿 697 2022-05-29

      Table of Contents

      Python Bindings Overview

      Marshalling Data Types

      Understanding Mutable and Immutable Values

      Managing Memory

      Setting Up Your Environment

      Using the invoke Tool

      C or C++ Source

      ctypes

      How It’s Installed

      Calling the Function

      Strengths and Weaknesses

      CFFI

      How It’s Installed

      Calling the Function

      Strengths and Weaknesses

      PyBind11

      How It’s Installed

      Calling the Function

      Strengths and Weaknesses

      Cython

      How It’s Installed

      Calling the Function

      Strengths and Weaknesses

      Other Solutions

      PyBindGen

      Boost.Python

      SIP

      Cppyy

      Shiboken

      SWIG

      Conclusion

      您是擁有想要從 Python 中使用的C或 C++ 庫的 Python 開發人員嗎?如果是這樣,那么Python 綁定允許您調用函數并將數據從 Python 傳遞到C或C++,讓您利用這兩種語言的優勢。在本教程中,您將看到一些可用于創建 Python 綁定的工具的概述。

      在本教程中,您將了解:

      為什么要從 Python調用 C 或 C++

      如何在 C 和 Python 之間傳遞數據

      哪些工具和方法可以幫助您創建 Python 綁定

      本教程面向中級 Python 開發人員。它假定讀者具備 Python 的基本知識,并對 C 或 C++ 中的函數和數據類型有所了解。您可以通過單擊下面的鏈接獲取本教程中將看到的所有示例代碼:

      Python Bindings概述

      在深入研究如何從 Python 調用 C之前,最好花一些時間了解為什么.?有幾種情況下,創建 Python 綁定來調用 C 庫是一個好主意:

      您已經擁有一個用 C++ 編寫的大型、經過測試的穩定庫,您想在 Python 中利用它。這可能是一個通信庫或一個與特定硬件對話的庫。它做什么并不重要。

      您希望通過將關鍵部分轉換為 C來加速 Python 代碼的特定部分。?C 不僅具有更快的執行速度,而且還允許您擺脫GIL的限制,前提是您小心。

      您想使用 Python 測試工具對其系統進行大規模測試。

      以上所有都是學習創建 Python 綁定以與 C 庫交互的重要原因。

      注意:在本教程中,您將創建到 C和C++ 的Python 綁定。大多數通用概念適用于兩種語言,因此除非兩種語言之間存在特定差異,否則將使用 C。通常,每個工具都支持 C或C++,但不能同時支持兩者。

      讓我們開始吧!

      編組數據類型

      等待!在開始編寫 Python 綁定之前,先看看 Python 和 C 如何存儲數據以及這會導致哪些類型的問題。首先,讓我們定義編組。這個概念由維基百科定義如下:

      將對象的內存表示轉換為適合存儲或傳輸的數據格式的過程。(來源)

      出于您的目的,編組是 Python 綁定在準備數據以將其從 Python 移動到 C 或反之亦然時所做的工作。Python 綁定需要進行編組,因為 Python 和 C 以不同的方式存儲數據。C 在內存中以最緊湊的形式存儲數據。如果您使用uint8_t,那么它總共將只使用 8 位內存。

      另一方面,在 Python 中,一切都是對象。這意味著每個整數在內存中使用幾個字節。多少取決于您運行的 Python 版本、操作系統和其他因素。這意味著您的 Python 綁定將需要為每個跨邊界傳遞的整數將C 整數轉換為Python 整數。

      其他數據類型在這兩種語言之間具有相似的關系。讓我們依次來看看:

      整數存儲計數數字。Python 以任意精度存儲整數,這意味著您可以存儲非常非常大的數字。C 指定整數的確切大小。在語言之間移動時需要注意數據大小,以防止 Python 整數值溢出 C 整數變量。

      浮點數是帶有小數位的數字。Python 可以存儲比 C 大得多(和小得多)的浮點數。這意味著您還必須注意這些值以確保它們保持在范圍內。

      復數是帶有虛部的數字。雖然 Python 具有內置復數,而 C 具有復數,但沒有用于在它們之間編組的內置方法。要封送復數,您需要在 C 代碼中構建struct或class來管理它們。

      字符串是字符序列。作為這樣一種常見的數據類型,當您創建 Python 綁定時,字符串將被證明是相當棘手的。與其他數據類型一樣,Python 和 C 以完全不同的格式存儲字符串。(與其他數據類型不同,這也是 C 和 C++ 不同的領域,這增加了樂趣!)您將研究的每個解決方案都有略微不同的處理字符串的方法。

      布爾變量只能有兩個值。由于它們在 C 中得到支持,因此將它們編組將被證明是相當簡單的。

      除了數據類型轉換之外,在構建 Python 綁定時還需要考慮其他問題。讓我們繼續探索它們。

      了解可變和不可變值

      除了所有這些數據類型之外,您還必須了解 Python 對象如何可變或不可變。當談到傳值或傳引用時,C 有一個類似的函數參數概念。在 C 中,所有參數都是按值傳遞的。如果要允許函數更改調用方中的變量,則需要傳遞指向該變量的指針。

      您可能想知道是否可以通過使用指針將不可變對象簡單地傳遞給 C 來繞過不可變限制。除非你走到丑陋和不可移植的極端,否則Python 不會給你一個指向 object 的指針,所以這行不通。如果您想用 C 修改 Python 對象,那么您需要采取額外的步驟來實現這一點。這些步驟將取決于您使用的工具,如下所示。

      因此,您可以將不變性添加到您創建 Python 綁定時要考慮的項目清單中。在創建此清單的宏偉之旅中,您的最后一站是如何處理 Python 和 C 處理內存管理的不同方式。

      管理內存

      C 和 Python管理內存的方式不同。在 C 中,開發人員必須管理所有內存分配并確保它們被釋放一次且僅一次。Python 使用垃圾收集器為您處理這個問題。

      雖然這些方法中的每一種都有其優點,但它確實為創建 Python 綁定添加了額外的麻煩。您需要知道每個對象的內存分配在哪里,并確保它只在語言障礙的同一側被釋放。

      例如,當您設置x = 3.?用于此的內存在 Python 端分配,需要進行垃圾收集。幸運的是,使用 Python 對象,很難做任何其他事情。看看 C 中的逆向,直接分配一塊內存:

      int* iPtr = (int*)malloc(sizeof(int));

      執行此操作時,您需要確保在 C 中釋放此指針。這可能意味著手動將代碼添加到 Python 綁定中以執行此操作。

      這完善了您的一般主題清單。讓我們開始設置您的系統,以便您可以編寫一些代碼!

      設置您的環境

      在本教程中,您將使用來自 Real Python GitHub 存儲庫的預先存在的 C 和 C++ 庫來展示每個工具的測試。目的是您將能夠將這些想法用于任何 C 庫。要遵循此處的所有示例,您需要具備以下條件:

      安裝的C++ 庫和命令行調用路徑的知識

      Python開發工具:

      對于 Linux,這是python3-dev或python3-devel包,具體取決于您的發行版。

      對于 Windows,有多個選項。

      Python 3.6或更高版本

      一個虛擬環境(建議,但不要求)

      Python 綁定:從 Python 調用 C 或 C++ |【生長吧!Python!】 【生長吧!Python】有獎征文火熱進行中:https://bbs.huaweicloud.com/blogs/278897

      該invoke工具

      最后一個對你來說可能是新的,所以讓我們仔細看看它。

      使用invoke工具

      invoke是您將在本教程中用于構建和測試 Python 綁定的工具。它具有類似的目的,make但使用 Python 而不是 Makefiles。您需要invoke使用pip以下命令在虛擬環境中安裝:

      $ python3 -m pip install invoke

      要運行它,請鍵入invoke后跟要執行的任務:

      $ invoke build-cmult ================================================== = Building C Library * Complete

      要查看哪些任務可用,請使用以下--list選項:

      $ invoke --list Available tasks: all Build and run all tests build-cffi Build the CFFI Python bindings build-cmult Build the shared library for the sample C code build-cppmult Build the shared library for the sample C++ code build-cython Build the cython extension module build-pybind11 Build the pybind11 wrapper library clean Remove any built objects test-cffi Run the script to test CFFI test-ctypes Run the script to test ctypes test-cython Run the script to test Cython test-pybind11 Run the script to test PyBind11

      請注意,當您查看定義任務的tasks.py文件時invoke,您會看到列出的第二個任務的名稱是build_cffi.?但是,來自的輸出將其--list顯示為build-cffi.?減號 (?-) 不能用作 Python 名稱的一部分,因此該文件使用下劃線 (?_) 代替。

      對于您將檢查的每個工具,都會定義一個build-和一個test-任務。例如,要運行 的代碼CFFI,您可以鍵入invoke build-cffi test-cffi。一個例外是ctypes,因為 沒有構建階段ctypes。此外,為了方便,還添加了兩個特殊任務:

      invoke all?運行所有工具的構建和測試任務。

      invoke clean?刪除任何生成的文件。

      既然您已經對如何運行代碼有所了解,那么在查看工具概述之前,讓我們先看一下您將要包裝的 C 代碼。

      C 或 C++ 源代碼

      在下面的每個示例部分中,您將為C 或 C++ 中的相同函數創建 Python 綁定。這些部分旨在讓您體驗每種方法的外觀,而不是有關該工具的深入教程,因此您將封裝的函數很小。您將為其創建 Python 綁定的函數將 anint和 afloat作為輸入參數并返回一個float是兩個數字的乘積:

      // cmult.c float cmult(int int_param, float float_param) { float return_value = int_param * float_param; printf(" In cmult : int: %d float %.1f returning %.1f\n", int_param, float_param, return_value); return return_value; }

      C 和 C++ 函數幾乎相同,它們之間的名稱和字符串略有不同。您可以通過單擊以下鏈接獲取所有代碼的副本:

      現在您已經克隆了 repo 并安裝了工具,您可以構建和測試這些工具。因此,讓我們深入了解下面的每個部分!

      ctypes

      您將從 開始ctypes,它是標準庫中用于創建 Python 綁定的工具。它提供了一個低級工具集,用于在 Python 和 C 之間加載共享庫和編組數據。

      它是如何安裝的

      的一大優點ctypes是它是 Python 標準庫的一部分。它是在 Python 2.5 版中添加的,因此您很可能已經擁有它。您可以import像使用sys或time模塊一樣。

      調用函數

      加載 C 庫和調用函數的所有代碼都將在 Python 程序中。這很棒,因為您的過程中沒有額外的步驟。您只需運行您的程序,一切都會得到處理。要在 中創建 Python 綁定ctypes,您需要執行以下步驟:

      加載您的庫。

      包裝一些輸入參數。

      告訴?ctypes你函數的返回類型。

      您將依次查看其中的每一個。

      ctypes為您提供了多種加載共享庫的方法,其中一些是特定于平臺的。對于您的示例,您將ctypes.CDLL通過傳入所需共享庫的完整路徑來直接創建對象:

      # ctypes_test.py import ctypes import pathlib if __name__ == "__main__": # Load the shared library into ctypes libname = pathlib.Path().absolute() / "libcmult.so" c_lib = ctypes.CDLL(libname)

      這適用于共享庫與 Python 腳本位于同一目錄中的情況,但在嘗試加載來自 Python 綁定以外的包的庫時要小心。在ctypes特定于平臺和特定情況的文檔中,有許多關于加載庫和查找路徑的詳細信息。

      注意:在庫加載過程中可能會出現許多特定于平臺的問題。最好在示例工作后進行增量更改。

      現在您已將庫加載到 Python 中,您可以嘗試調用它!

      請記住,您的 C 函數的函數原型如下:

      // cmult.h float cmult(int int_param, float float_param);

      您需要傳入一個整數和一個浮點數,并且可以期望得到一個浮點數返回。整數和浮點數在 Python 和 C 中都有本機支持,因此您希望這種情況適用于合理的值。

      將庫加載到 Python 綁定中后,該函數將成為 的屬性c_lib,即CDLL您之前創建的對象。您可以嘗試這樣稱呼它:

      x, y = 6, 2.3 answer = c_lib.cmult(x, y)

      哎呀!這不起作用。此行在示例 repo 中被注釋掉,因為它失敗了。如果您嘗試使用該調用運行,那么 Python 會報錯:

      $ invoke test-ctypes Traceback (most recent call last): File "ctypes_test.py", line 16, in answer = c_lib.cmult(x, y) ctypes.ArgumentError: argument 2: : Don't know how to convert parameter 2

      看起來您需要說明ctypes任何不是整數的參數。ctypes除非您明確告訴它,否則您對該函數一無所知。任何未以其他方式標記的參數都假定為整數。ctypes不知道如何將2.3存儲的值轉換為y整數,所以它失敗了。

      要解決此問題,您需要c_float從號碼中創建一個。您可以在調用函數的行中執行此操作:

      # ctypes_test.py answer = c_lib.cmult(x, ctypes.c_float(y)) print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")

      現在,當您運行此代碼時,它會返回您傳入的兩個數字的乘積:

      $ invoke test-ctypes In cmult : int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 48.0

      等一下……6乘以2.3不是48.0!

      事實證明,就像輸入參數一樣,ctypes?假設您的函數返回一個int.?實際上,您的函數返回 a?float,它被錯誤地編組。就像輸入參數一樣,您需要告訴ctypes使用不同的類型。這里的語法略有不同:

      # ctypes_test.py c_lib.cmult.restype = ctypes.c_float answer = c_lib.cmult(x, ctypes.c_float(y)) print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")

      這應該夠了吧。讓我們運行整個test-ctypes目標,看看你有什么。請記住,輸出的第一部分是在restype將函數固定為浮點數之前:

      $ invoke test-ctypes ================================================== = Building C Library * Complete ================================================== = Testing ctypes Module In cmult : int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 48.0 In cmult : int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 13.8

      這樣更好!雖然第一個未更正的版本返回錯誤的值,但您的固定版本與 C 函數一致。C 和 Python 都得到相同的結果!現在它可以工作了,看看為什么您可能想或不想使用ctypes.

      長處和短處

      ctypes與您將在此處檢查的其他工具相比,最大的優勢在于它內置于標準庫中。它還不需要額外的步驟,因為所有工作都是作為 Python 程序的一部分完成的。

      此外,所使用的概念是低級的,這使得像您剛剛做的那樣的練習易于管理。然而,由于缺乏自動化,更復雜的任務變得繁瑣。在下一部分中,您將看到一個工具,該工具為流程添加了一些自動化。

      CFFI

      CFFI是Python的C 外來函數接口。生成 Python 綁定需要更自動化的方法。CFFI有多種方式可以構建和使用 Python 綁定。有兩種不同的選項可供選擇,為您提供四種可能的模式:

      ABI vs API:?API 模式使用 C 編譯器生成完整的 Python 模塊,而 ABI 模式加載共享庫并直接與其交互。在不運行編譯器的情況下,獲取正確的結構和參數很容易出錯。該文檔強烈建議使用 API 模式。

      內聯 vs 外聯:這兩種模式的區別在于速度和便利性之間的權衡:

      每次運行腳本時,內聯模式都會編譯 Python 綁定。這很方便,因為您不需要額外的構建步驟。但是,它確實會減慢您的程序速度。

      Out-of-line 模式需要一個額外的步驟來一次性生成 Python 綁定,然后在每次程序運行時使用它們。這要快得多,但這對您的應用程序可能無關緊要。

      對于此示例,您將使用 API 外聯模式,它生成最快的代碼,并且通常看起來類似于您將在本教程后面創建的其他 Python 綁定。

      它是如何安裝的

      由于CFFI不是標準庫的一部分,您需要在您的機器上安裝它。建議您為此創建一個虛擬環境。幸運的是,CFFI安裝有pip:

      $ python3 -m pip install cffi

      這會將軟件包安裝到您的虛擬環境中。如果您已經從 安裝requirements.txt,那么應該注意這一點。您可以requirements.txt通過訪問以下鏈接中的 repo 來查看:

      獲取示例代碼:?單擊此處獲取您將用于在本教程中了解 Python 綁定的示例代碼。

      現在你已經CFFI安裝好了,是時候試一試了!

      調用函數

      與 不同的是ctypes,CFFI您正在創建一個完整的 Python 模塊。您將能夠import像標準庫中的任何其他模塊一樣使用該模塊。您需要做一些額外的工作來構建 Python 模塊。要使用CFFIPython 綁定,您需要執行以下步驟:

      編寫一些描述綁定的 Python 代碼。

      運行該代碼以生成可加載模塊。

      修改調用代碼以導入和使用新創建的模塊。

      這可能看起來需要做很多工作,但您將完成這些步驟中的每一個,并了解它是如何工作的。

      CFFI提供讀取C 頭文件的方法,以在生成 Python 綁定時完成大部分工作。在 的文檔中CFFI,執行此操作的代碼放置在單獨的 Python 文件中。對于此示例,您將直接將該代碼放入構建工具中invoke,該工具使用 Python 文件作為輸入。要使用CFFI,您首先要創建一個cffi.FFI對象,該對象提供了您需要的三種方法:

      # tasks.py import cffi ... """ Build the CFFI Python bindings """ print_banner("Building CFFI Module") ffi = cffi.FFI()

      擁有 FFI 后,您將使用.cdef()來自動處理頭文件的內容。這會為您創建包裝函數以從 Python 封送數據:

      # tasks.py this_dir = pathlib.Path().absolute() h_file_name = this_dir / "cmult.h" with open(h_file_name) as h_file: ffi.cdef(h_file.read())

      讀取和處理頭文件是第一步。之后,您需要使用.set_source()來描述CFFI將生成的源文件:

      # tasks.py ffi.set_source( "cffi_example", # Since you're calling a fully-built library directly, no custom source # is necessary. You need to include the .h files, though, because behind # the scenes cffi generates a .c file that contains a Python-friendly # wrapper around each of the functions. '#include "cmult.h"', # The important thing is to include the pre-built lib in the list of # libraries you're linking against: libraries=["cmult"], library_dirs=[this_dir.as_posix()], extra_link_args=["-Wl,-rpath,."], )

      以下是您傳入的參數的細分:

      "cffi_example"是將在您的文件系統上創建的源文件的基本名稱。CFFI將生成一個.c文件,將其編譯為一個.o文件,并將其鏈接到一個..so或..dll文件。

      '#include "cmult.h"'是自定義 C 源代碼,它將在編譯之前包含在生成的源代碼中。在這里,您只需包含.h要為其生成綁定的文件,但這可用于一些有趣的自定義。

      libraries=["cmult"]告訴鏈接器您預先存在的 C 庫的名稱。這是一個列表,因此您可以根據需要指定多個庫。

      library_dirs=[this_dir.as_posix(),]?是一個目錄列表,告訴鏈接器在何處查找上述庫列表。

      extra_link_args=['-Wl,-rpath,.']是一組生成共享對象的選項,它將在當前路徑 (?.) 中查找它需要加載的其他庫。

      調用.set_source()不會構建 Python 綁定。它只設置元數據來描述將生成的內容。要構建 Python 綁定,您需要調用.compile():

      # tasks.py ffi.compile()

      這通過生成.c文件、.o文件和共享庫來完成。在invoke你剛走通過任務可以在上運行命令行構建Python綁定:

      $ invoke build-cffi ================================================== = Building C Library * Complete ================================================== = Building CFFI Module * Complete

      你有你的CFFIPython 綁定,所以是時候運行這段代碼了!

      在您為配置和運行CFFI編譯器所做的所有工作之后,使用生成的 Python 綁定看起來就像使用任何其他 Python 模塊一樣:

      # cffi_test.py import cffi_example if __name__ == "__main__": # Sample data for your call x, y = 6, 2.3 answer = cffi_example.lib.cmult(x, y) print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")

      你導入新模塊,然后就可以cmult()直接調用了。要對其進行測試,請使用以下test-cffi任務:

      $ invoke test-cffi ================================================== = Testing CFFI Module In cmult : int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 13.8

      這將運行您的cffi_test.py程序,該程序會測試您使用CFFI.?關于編寫和使用CFFIPython 綁定的部分到此結束。

      長處和短處

      ctypes與CFFI您剛剛看到的示例相比,這似乎需要更少的工作。雖然這對于這個用例來說是正確的,CFFI但與ctypes由于大部分功能包裝的自動化相比,它可以更好地擴展到更大的項目。

      CFFI也產生了完全不同的用戶體驗。ctypes允許您將預先存在的 C 庫直接加載到您的 Python 程序中。CFFI,另一方面,創建一個可以像其他 Python 模塊一樣加載的新 Python 模塊。

      更重要的是,使用上面使用的外部 API方法,創建 Python 綁定的時間損失在您構建它時完成一次,并且不會在每次運行代碼時發生。對于小程序來說,這可能不是什么大問題,但也可以通過CFFI這種方式更好地擴展到更大的項目。

      就像ctypes, usingCFFI只允許您直接與 C 庫交互。C++ 庫需要大量的工作才能使用。在下一節中,您將看到一個專注于 C++ 的 Python 綁定工具。

      PyBind11

      PyBind11使用完全不同的方法來創建 Python 綁定。除了將重點從 C 轉移到 C++ 之外,它還使用 C++ 來指定和構建模塊,使其能夠利用 C++ 中的元編程工具。像 一樣CFFI,生成的 Python 綁定PyBind11是一個完整的 Python 模塊,可以直接導入和使用。

      PyBind11以Boost::Python庫為藍本并具有類似的界面。但是,它將其使用限制為 C++11 和更新版本,與支持所有內容的 Boost 相比,這使其能夠簡化和加快處理速度。

      它是如何安裝的

      文檔的“第一步”部分將PyBind11引導您了解如何下載和構建PyBind11.?雖然這似乎不是嚴格要求,但完成這些步驟將確保您設置了正確的 C++ 和 Python 工具。

      注:大部分示例PyBind11使用cmake,是構建 C 和 C++ 項目的好工具。但是,對于此演示,您將繼續使用該invoke工具,該工具遵循文檔的手動構建部分中的說明。

      您需要將此工具安裝到您的虛擬環境中:

      $ python3 -m pip install pybind11

      PyBind11是一個全頭庫,類似于 Boost 的大部分內容。這允許pip將庫的實際 C++ 源代碼直接安裝到您的虛擬環境中。

      調用函數

      在您深入研究之前,請注意您使用的是不同的 C++ 源文件,?cppmult.cpp,而不是您用于前面示例的 C 文件。兩種語言的功能基本相同。

      與 類似CFFI,您需要創建一些代碼來告訴該工具如何構建您的 Python 綁定。與 不同CFFI,此代碼將使用 C++ 而不是 Python。幸運的是,只需要很少的代碼:

      // pybind11_wrapper.cpp #include #include PYBIND11_MODULE(pybind11_example, m) { m.doc() = "pybind11 example plugin"; // Optional module docstring m.def("cpp_function", &cppmult, "A function that multiplies two numbers"); }

      讓我們一次一個地看,因為PyBind11將大量信息打包成幾行。

      前兩行包括pybind11.hC++ 庫的文件和頭文件cppmult.hpp.?之后,你就有了PYBIND11_MODULE宏。這將擴展為PyBind11源代碼中詳細描述的 C++ 代碼塊:

      此宏創建入口點,當 Python 解釋器導入擴展模塊時將調用該入口點。模塊名稱作為第一個參數給出,不應用引號引起來。第二個宏參數定義了一個py::module可用于初始化模塊的類型變量。(來源)

      這對您來說意味著,在本例中,您正在創建一個名為的模塊pybind11_example,其余代碼將m用作py::module對象的名稱。在下一行,在您定義的 C++ 函數中,您為模塊創建一個文檔字符串。雖然這是可選的,但讓您的模塊更加Pythonic是一個不錯的選擇。

      最后,你有m.def()電話。這將定義一個由您的新 Python 綁定導出的函數,這意味著它將在 Python 中可見。在此示例中,您將傳遞三個參數:

      cpp_function是您將在 Python 中使用的函數的導出名稱。如本例所示,它不需要匹配 C++ 函數的名稱。

      &cppmult?獲取要導出的函數的地址。

      "A function..."?是函數的可選文檔字符串。

      現在您已經有了 Python 綁定的代碼,接下來看看如何將其構建到 Python 模塊中。

      用于構建 Python 綁定的工具PyBind11是 C++ 編譯器本身。您可能需要修改編譯器和操作系統的默認值。

      首先,您必須構建要為其創建綁定的 C++ 庫。對于這么小的示例,您可以將cppmult庫直接構建到 Python 綁定庫中。但是,對于大多數實際示例,您將有一個要包裝的預先存在的庫,因此您將cppmult單獨構建該庫。構建是對編譯器的標準調用以構建共享庫:

      # tasks.py invoke.run( "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC cppmult.cpp " "-o libcppmult.so " )

      運行這個invoke build-cppmult產生libcppmult.so:

      $ invoke build-cppmult ================================================== = Building C++ Library * Complete

      另一方面,Python 綁定的構建需要一些特殊的細節:

      1# tasks.py 2invoke.run( 3 "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC " 4 "`python3 -m pybind11 --includes` " 5 "-I /usr/include/python3.7 -I . " 6 "{0} " 7 "-o {1}`python3.7-config --extension-suffix` " 8 "-L. -lcppmult -Wl,-rpath,.".format(cpp_name, extension_name) 9)

      讓我們逐行瀏覽一下。第 3 行包含相當標準的 C++ 編譯器標志,指示幾個細節,包括您希望捕獲所有警告并將其視為錯誤、您需要共享庫以及您使用的是 C++11。

      第 4 行是魔法的第一步。它調用pybind11模塊使其include為PyBind11.?您可以直接在控制臺上運行此命令以查看它的作用:

      $ python3 -m pybind11 --includes -I/home/jima/.virtualenvs/realpython/include/python3.7m -I/home/jima/.virtualenvs/realpython/include/site/python3.7

      您的輸出應該相似但顯示不同的路徑。

      在編譯調用的第 5 行,您可以看到您還添加了 Python dev 的路徑includes。雖然建議您不要鏈接 Python 庫本身,但源代碼需要一些代碼Python.h才能發揮其魔力。幸運的是,它使用的代碼在 Python 版本中相當穩定。

      第 5 行還用于-I .將當前目錄添加到include路徑列表中。這允許#include 解析包裝器代碼中的行。

      第 6 行指定源文件的名稱,即pybind11_wrapper.cpp.?然后,在第 7 行,您會看到更多的構建魔法正在發生。此行指定輸出文件的名稱。Python 在模塊命名上有一些特別的想法,包括 Python 版本、機器架構和其他細節。Python 還提供了一個工具來幫助解決這個問題python3.7-config:

      $ python3.7-config --extension-suffix .cpython-37m-x86_64-linux-gnu.so

      如果您使用的是不同版本的 Python,則可能需要修改該命令。如果您使用不同版本的 Python 或在不同的操作系統上,您的結果可能會發生變化。

      構建命令的最后一行,第 8 行,將鏈接器指向libcppmult您之前構建的庫。該rpath部分告訴鏈接器向共享庫添加信息以幫助操作系統libcppmult在運行時查找。最后,您會注意到此字符串的格式為cpp_name和extension_name。Cython在下一節中構建 Python 綁定模塊時,您將再次使用此函數。

      運行此命令以構建綁定:

      $ invoke build-pybind11 ================================================== = Building C++ Library * Complete ================================================== = Building PyBind11 Module * Complete

      就是這樣!您已經使用PyBind11.?是時候測試一下了!

      與CFFI上面的示例類似,一旦您完成了創建 Python 綁定的繁重工作,調用您的函數看起來就像普通的 Python 代碼:

      # pybind11_test.py import pybind11_example if __name__ == "__main__": # Sample data for your call x, y = 6, 2.3 answer = pybind11_example.cpp_function(x, y) print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")

      由于您pybind11_example在PYBIND11_MODULE宏中用作模塊的名稱,因此這就是您導入的名稱。在m.def()您告訴PyBind11將cppmult函數導出為 的調用中cpp_function,這就是您用來從 Python 調用它的方法。

      你也可以測試它invoke:

      $ invoke test-pybind11 ================================================== = Testing PyBind11 Module In cppmul: int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 13.8

      這就是PyBind11看起來的樣子。接下來,您將了解何時以及為何PyBind11是適合該工作的工具。

      長處和短處

      PyBind11專注于 C++ 而不是 C,這使得它不同于ctypes和CFFI。它有幾個特性使其對 C++ 庫非常有吸引力:

      它支持類。

      它處理多態子類化。

      它允許您從 Python 和許多其他工具向對象添加動態屬性,而使用您檢查過的基于 C 的工具很難做到這一點。

      話雖如此,您需要進行大量設置和配置才能PyBind11啟動和運行。正確安裝和構建可能有點挑剔,但一旦完成,它似乎相當可靠。此外,PyBind11要求您至少使用 C++11 或更高版本。對于大多數項目來說,這不太可能是一個很大的限制,但它可能是您的一個考慮因素。

      最后,創建 Python 綁定需要編寫的額外代碼是用 C++ 編寫的,而不是用 Python 編寫的。這可能是也可能不是你的問題,但它是比你在這里看到的其他工具不同。在下一節中,您將繼續討論Cython,它采用完全不同的方法來解決這個問題。

      Cython

      該方法Cython需要創建Python綁定使用類Python語言來定義綁定,然后生成的C或C ++代碼可被編譯成模塊。有幾種方法可以使用Cython.?最常見的一種是使用setupfrom?distutils。對于此示例,您將堅持使用該invoke工具,它允許您使用運行的確切命令。

      它是如何安裝的

      Cython是一個 Python 模塊,可以從PyPI安裝到您的虛擬環境中:

      $ python3 -m pip install cython

      同樣,如果您已將該requirements.txt文件安裝到虛擬環境中,則該文件已經存在。您可以requirements.txt通過單擊以下鏈接獲取副本:

      獲取示例代碼:?單擊此處獲取您將用于在本教程中了解 Python 綁定的示例代碼。

      這應該讓你準備好與之合作Cython!

      調用函數

      要使用 構建 Python 綁定Cython,您將遵循與用于CFFI和 的步驟類似的步驟PyBind11。您將編寫綁定、構建它們,然后運行 Python 代碼來調用它們。Cython可以同時支持 C 和 C++。對于本示例,您將使用cppmult您在PyBind11上面的示例中使用的庫。

      聲明模塊的最常見形式Cython是使用.pyx文件:

      1# cython_example.pyx 2""" Example cython interface definition """ 3 4cdef extern from "cppmult.hpp": 5 float cppmult(int int_param, float float_param) 6 7def pymult( int_param, float_param ): 8 return cppmult( int_param, float_param )

      這里有兩個部分:

      線3和4告訴Cython您使用的是cppmult()從cppmult.hpp。

      第 6 行和第 7 行創建了一個包裝函數pymult(),以調用cppmult()。

      這里使用的語言是 C、C++ 和 Python 的特殊組合。不過,對于 Python 開發人員來說,它看起來相當熟悉,因為其目標是使過程更容易。

      第一部分 withcdef extern...告訴Cython下面的函數聲明也可以在cppmult.hpp文件中找到。這對于確保根據與 C++ 代碼相同的聲明構建 Python 綁定非常有用。第二部分看起來像一個普通的 Python 函數——因為它是!本節創建一個可以訪問 C++ 函數的 Python 函數cppmult。

      現在您已經定義了 Python 綁定,是時候構建它們了!

      的構建過程Cython與您使用的構建過程相似PyBind11。您首先Cython在.pyx文件上運行以生成.cpp文件。完成此操作后,您可以使用用于以下內容的相同函數對其進行編譯PyBind11:

      1# tasks.py 2def compile_python_module(cpp_name, extension_name): 3 invoke.run( 4 "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC " 5 "`python3 -m pybind11 --includes` " 6 "-I /usr/include/python3.7 -I . " 7 "{0} " 8 "-o {1}`python3.7-config --extension-suffix` " 9 "-L. -lcppmult -Wl,-rpath,.".format(cpp_name, extension_name) 10 ) 11 12def build_cython(c): 13 """ Build the cython extension module """ 14 print_banner("Building Cython Module") 15 # Run cython on the pyx file to create a .cpp file 16 invoke.run("cython --cplus -3 cython_example.pyx -o cython_wrapper.cpp") 17 18 # Compile and link the cython wrapper library 19 compile_python_module("cython_wrapper.cpp", "cython_example") 20 print("* Complete")

      您首先運行cython您的.pyx文件。您可以在此命令上使用幾個選項:

      --cplus?告訴編譯器生成 C++ 文件而不是 C 文件。

      -3切換Cython到生成 Python 3 語法而不是 Python 2。

      -o cython_wrapper.cpp?指定要生成的文件的名稱。

      生成 C++ 文件后,您可以使用 C++ 編譯器生成 Python 綁定,就像您為PyBind11.?請注意,include使用該pybind11工具生成額外路徑的調用仍在該函數中。在這里不會有任何傷害,因為您的來源不需要這些。

      在 中運行此任務invoke會產生以下輸出:

      $ invoke build-cython ================================================== = Building C++ Library * Complete ================================================== = Building Cython Module * Complete

      可以看到它構建了cppmult庫,然后構建了cython模塊來包裝它。現在你有了CythonPython 綁定。(試著說的是迅速...)它的時間來測試一下吧!

      調用新 Python 綁定的 Python 代碼與用于測試其他模塊的代碼非常相似:

      1# cython_test.py 2import cython_example 3 4# Sample data for your call 5x, y = 6, 2.3 6 7answer = cython_example.pymult(x, y) 8print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")

      第 2 行導入新的 Python 綁定模塊,并pymult()在第 7 行調用。請記住,該.pyx文件提供了一個 Python 包裝器cppmult()并將其重命名為pymult.?使用 invoke 運行您的測試會產生以下結果:

      $ invoke test-cython ================================================== = Testing Cython Module In cppmul: int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 13.8

      你得到和以前一樣的結果!

      長處和短處

      Cython是一個相對復雜的工具,可以在為 C 或 C++ 創建 Python 綁定時為您提供更深層次的控制。雖然您沒有在此處深入介紹它,但它提供了一種 Python 式的方法來編寫手動控制GIL 的代碼,這可以顯著加快某些類型的問題的處理速度。

      然而,這種 Python 風格的語言并不完全是 Python,因此當您要快速確定 C 和 Python 的哪些部分適合何處時,會有一個輕微的學習曲線。

      其他解決方案

      在研究本教程時,我遇到了幾種用于創建 Python 綁定的不同工具和選項。雖然我將此概述限制為一些更常見的選項,但我偶然發現了其他幾種工具。下面的列表并不全面。如果上述工具之一不適合您的項目,這只是其他可能性的一個示例。

      PyBindGen

      PyBindGen為 C 或 C++ 生成 Python 綁定并用 Python 編寫。它旨在生成可讀的 C 或 C++ 代碼,這應該可以簡化調試問題。目前尚不清楚這是否最近已更新,因為文檔將 Python 3.4 列為最新的測試版本。然而,在過去的幾年里,每年都有發布。

      Boost.Python

      Boost.Python有一個類似于PyBind11您在上面看到的界面。這不是巧合,因為PyBind11它基于這個庫!Boost.Python是用完整的 C++ 編寫的,并且在大多數平臺上支持大多數(如果不是全部)C++ 版本。相比之下,PyBind11僅限于現代 C++。

      SIP

      SIP是為PyQt項目開發的用于生成 Python 綁定的工具集。wxPython項目也使用它來生成它們的綁定。它有一個代碼生成工具和一個額外的 Python 模塊,為生成的代碼提供支持功能。

      Cppyy

      “cppyy 背后的最初想法(追溯到 2001 年)是允許生活在 C++ 世界中的 Python 程序員訪問那些 C++ 包,而不必直接接觸 C++(或等待 C++ 開發人員過來并提供綁定) 。”?(來源)

      Shiboken

      Shiboken是為與 Qt 項目關聯的 PySide 項目開發的用于生成 Python 綁定的工具。雖然它被設計為該項目的工具,但文檔表明它既不是 Qt 也不是 PySide 特定的,可用于其他項目。

      SWIG

      SWIG是與此處列出的任何其他工具不同的工具。它是一個通用工具,用于為許多其他語言(而不僅僅是 Python)創建到 C 和 C++ 程序的綁定。這種為不同語言生成綁定的能力在某些項目中非常有用。當然,就復雜性而言,它會帶來成本。

      結論

      恭喜!您現在已經大致了解了用于創建Python 綁定的幾個不同選項。您已經了解了編組數據以及創建綁定時需要考慮的問題。您已經了解了如何使用以下工具從 Python 調用 C 或 C++ 函數:

      ctypes

      CFFI

      PyBind11

      Cython

      您現在知道,雖然ctypes允許您直接加載 DLL 或共享庫,但其他三個工具需要額外的步驟,但仍會創建完整的 Python 模塊。作為獎勵,您還使用了invoke從 Python 運行命令行任務的工具。您可以通過單擊下面的鏈接獲取在本教程中看到的所有代碼:

      【生長吧!Python】有獎征文火熱進行中:https://bbs.huaweicloud.com/blogs/278897

      C++ Python

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:【精選單品】基于華為鯤鵬生態的ICT綜合實訓平臺有哪些優勢?
      下一篇:Java認知一——基礎語法
      相關文章
      亚洲国产区男人本色| 亚洲美免无码中文字幕在线| 亚洲精品二三区伊人久久| 亚洲av无码专区在线播放| 亚洲日韩精品一区二区三区| 亚洲精品高清在线| 国产AV日韩A∨亚洲AV电影| 99亚洲精品卡2卡三卡4卡2卡| 亚洲高清国产拍精品熟女| 亚洲精品美女久久久久久久| 亚洲欧美成人一区二区三区| 亚洲欧美国产国产综合一区| 亚洲av无码专区国产不乱码| 亚洲av永久无码一区二区三区| 亚洲av色香蕉一区二区三区蜜桃| 亚洲国产日韩精品| 国内精品久久久久影院亚洲| 亚洲一线产品二线产品| 亚洲精品动漫免费二区| 九九精品国产亚洲AV日韩| 伊人久久亚洲综合影院| 亚洲精品WWW久久久久久 | 亚洲国产综合在线| 久久久久亚洲精品无码蜜桃| 亚洲久本草在线中文字幕| 亚洲欧洲日产韩国在线| 亚洲中文无码a∨在线观看| 亚洲 暴爽 AV人人爽日日碰 | 亚洲人成人伊人成综合网无码| 亚洲欧美日韩久久精品| 亚洲?V乱码久久精品蜜桃| 国产日产亚洲系列最新| 国产AV无码专区亚洲AV毛网站| 亚洲国产第一页www| 亚洲国产成人综合| 亚洲精品第一国产综合亚AV| 亚洲国产成人精品久久久国产成人一区二区三区综 | 在线播放亚洲第一字幕| 亚洲AV日韩AV天堂久久 | 亚洲成片观看四虎永久| 亚洲精品美女久久久久99|