pythonはgoまたはcを呼び出す
この文書は、リトル・ミンが個人的な学習のために編集したものです。
記事のリンク https://blog.csdn.net/as604049322/article/details/112058313
pdfダウンロードはこちら https://download.csdn.net/download/as604049322/13999212
go言語へのpythonの呼び出し
Pythonは、最高の効率でほとんどのことを行うことができる、非常に生産的な言語ですが、Pythonのパフォーマンスは、我々は、特に大きなロックGILを批判されている問題です。もちろん、最近ほとんどのプログラムは、(IO)ネットワーク集約型のプログラムであり、Pythonはそのために十分ですが、もし我々が既に存在するプロジェクトや開発したい、そこに計算集約プログラムのシナリオを言う、パフォーマンスを改善するためには何ができるのですか?
一般的には、Pythonの計算負荷の高い部分をC++で書き換えて性能を向上させることができますが、C++は学習コストやポインタ、自分でメモリを解放する敷居の高さがあり、自動ガベージコレクションやもともと並行性が高いというメリットがあるGoが重宝されるんですね。
pythonのctypesモジュールはC互換のデータ型とso/dllダイナミックリンクライブラリファイルを読み込む関数を提供し、GO言語自体はこの2つの機能を元にC仕様に準拠したdllやsoダイナミックリンクライブラリをコンパイルできるので、パイソンを使って問題なくGO言語が呼び出せるのです。
Golangの環境設定
Goの公式ミラーサイトです。 https://golang.google.cn/dl/
デフォルトの最高バージョンを選択するだけで、Go コードは後方互換性があります。 は、その バージョン間の差は関係ない
インストールが正常に行われたかどうかを確認する
>go version
go version go1.15.2 windows/amd64
注
すでにバージョン1.11以上なので、今回は
go mod
を設定することなく、依存関係を管理することができます。
GOPATH
などと変なことを言っています。
GOPROXY(プロキシ)の設定
Goからパッケージを借りてダウンロードしたりする必要があるかもしれませんが、デフォルトの公式ソースの
GOPROXY=https://proxy.golang.org,direct
中国国内ではアクセスできない
入る
go env
Goのコンフィギュレーションを表示する。
>go env
...
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=D:\Go
...
国内ミラーサイトへ変更
go env -w GOPROXY=https://goproxy.cn,direct
もう一度、Goの設定を見る。
>go env
...
set GOPROXY=https://goproxy.cn,direct
set GOROOT=D:\Go
...
go言語クロスプラットフォームコンパイル
クロスプラットフォームコンパイルとも呼ばれ、WinプラットフォームでLinuxの実行ファイルにコンパイルすることができます。
これがGoの人気の理由です。java、python、phpなどの言語は一般的にwinプラットフォームで開発され、linuxに展開されるため、サードパーティの依存関係に対応するのが大変なんです。
Goを使えば、サードパーティの依存関係がどうであれ、最終的には実行ファイルにパッケージされ、直接、瞬時に、しかも高度に並列化された形でデプロイされるのです。
例
Linuxプラットフォームの実行ファイルをWindowsでコンパイルする。
cmd
の下で、以下のコマンドを順に実行します。
SET CGO_ENABLED=0 // Disable CGO
SET GOOS=linux // the target platform is linux
SET GOARCH=amd64 // target processor architecture is amd64
次に、以下を実行します。
go build
をクリックすると、Linux上で実行可能なファイルが得られます。
このファイルをlinuxのサーバーにアップロードすると、Go環境がなくても正常に実行できる。
Macプラットフォーム用の64ビット実行ファイルをWindowsでコンパイルする。
SET CGO_ENABLED=0
SET GOOS=darwin
SET GOARCH=amd64
go build
Mac上のLinuxおよびWindowsプラットフォーム用の64ビット実行ファイルをコンパイルします。
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
MacおよびWindowsプラットフォーム用の64ビット実行ファイルをLinuxでコンパイルします。
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
PythonとGoのパフォーマンス比較
最適化の効果をよりよく示すために、計算量の多いケースで2つの言語の違いを大まかに比較してみましょう。
テスト 大規模計算の累積シミュレーションを10億回(100000000回)個別に計算する。
Pythonのコードです。
import time
def run(n):
sum = 0
for i in range(n):
sum += i
print(sum)
if __name__ == '__main__':
startTime = time.time()
run(100000000)
endTime = time.time()
print("elapsed time:", endTime - startTime)
に5sほどかかった。
囲碁のコード
package main
import (
"fmt"
"time"
)
func run(n int) {
sum := 0
for i := 0; i < n; i++ {
sum += i
}
fmt.Println(sum)
}
func main() {
var startTime = time.Now()
run(100000000)
fmt.Println("elapsed time:", time.Since(startTime))
}
50ms程度かかります。
Python呼び出し可能な.soファイルへコンパイルされたGoコード
64ビット版gccツールMinGWのインストール
https://sourceforge.net/projects/mingw-w64/下载后 にアクセスし、ステップバイステップでインストールします。
オフラインのパッケージがBaidu Cloudにアップロードされました。
https://pan.baidu.com/s/1ZmjQUf5QcBbeHCi7mIrYxg 抽出コード:edc5
Windowsはx86_64-8.1.0-release-win32-seh-rt_v6-rev0に対応、直接解凍して環境変数にMinGWのbinディレクトリを追加すれば使えるようになります。
gccのバージョンを表示するには。
>gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=D:/develop/mingw64/bin/... /libexec/gcc/x86_64-w64-mingw32/8.1.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ... /... /... /src/gcc-8.1.0/configure --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --prefix=/mingw64 --with- sysroot=/c/mingw810/x86_64-810-win32-seh-rt_v6-rev0/mingw64 --enable-shared --enable-static --disable-multilib --enable-languages=c, c++,fortran,lto --enable-libstdcxx-time=yes --enable-threads=win32 --enable-libgomp --enable-libatomic --enable-lto --enable-graphite -- enable-checking=release --enable-fully-dynamic-string --enable-version-specific-runtime-libs --disable-libstdcxx-pch --disable- libstdcxx-debug --enable-bootstrap --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-gnu- as --with-gnu-ld --with-arch=nocona --with-tune=core2 --with-libiconv --with-system-zlib --with-gmp=/c/mingw810/prerequisites/x86_64-w64 --mingw32-static --with-mpfr=/c/mingw810/prerequisites/x86_64-w64 --mingw32-static --with-mpc=/c/mingw810/prerequisites/x86_64-w64- mingw32-static --with-isl=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-pkgversion='x86_64-win32-seh-rev0, Built by MinGW- W64 project' --with-bugurl=https://sourceforge.net/projects/mingw-w64 CFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/x86_64-810-win32-seh-rt _v6-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw32- static/include' CXXFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/x86_64-810-win32-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/ prerequisites/x86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_
.soファイルでコンパイルする必要があるgoのコードには、例えば、Cをインポートする必要があるなど、いくつかの要件があります。
package main
import (
"C" //C must be imported
)
//export run
func run(n int) int{
// The function must be externally interfaced via a comment in the export function name format
sum := 0
for i := 0; i < n; i++ {
sum += i
}
fmt.Println("I'm Go code, I'm done running, my result is:",sum)
return sum
}
func main() {
//main function do not write anything, and package name main to correspond to
}
Python 呼び出し用に .so ファイルにコンパイルします。
go build -buildmode=c-shared -o s1.so s1.go
形式を指定します。
go build -buildmode=c-shared -o output .so file go source file
以下のように.hファイルと.soファイルが生成され、.soファイルはPythonの呼び出しに利用できるようになります。
Ptyhonはsoファイルを呼び出します
上記で生成された.soファイルをPythonプロジェクトの同じ階層にコピーします。
書き方 s1.py で、やはり10億を計算し、肝心の部分はGoで生成された.soで実行されます。
from ctypes import *
import time
if __name__ == '__main__':
startTime = time.time()
s = CDLL("s1.so") # load s1.so file
result = s.run(100000000) # call the run function inside the .so file generated by Go
print("result:", result)
endTime = time.time()
print("elapsed time:", endTime - startTime)
総経過時間:約0.04s。
見ての通り、Python は高速ですが、Go が生成した .so ファイルを呼び出した後、驚くことに間違った戻り値を表示します。
しかし、例えば10023のように、いくつかの小さな数字を計算すると正しい結果が得られます。
.hファイルの探索
上記の問題は、デフォルトの戻り値型ストレージの範囲が限定されていることが原因ですが、以下、goコンパイルで生成されたc中間ファイルを解析することでその原因を探ります。
.hファイルを開いて、最後まで行ってみてください。
extern で始まる宣言を検索します。
extern GoInt run(GoInt n);
これは、先のgoソースコードで宣言されたrunメソッドをcコードに変換したところで、引数型と戻り値型がともにc言語のGoInt型であることを示しています。
型定義に目を向ける。
このように、GoIntは実際にはGoInt64であり、GoInt64はlong long型であることがわかります。
Pythonは
ctypes
モジュールに対応するテーブルを持つ .so ファイルを呼び出します。
参考:https://docs.python.org/zh-tw/3.7/library/ctypes.html
<テーブル ctypes タイプ C言語タイプ Pythonタイプ c_bool ブール ブール (1) c_char チャー 1文字バイトオブジェクト c_wchar wchar_t 一文字の文字列 c_byte チャー int c_ubyte 符号なし文字 int c_short 短い int c_ushort 符号なしショート int c_int int int c_uint 符号なしint int c_long 長い int c_ulong 符号なしロング int c_longlong __int64またはlong long int c_ulonglong 符号なし__int64または符号なしlong long イント c_size_t size_t int c_ssize_t ssize_t または Py_ssize_t int c_float フロート フロート c_double ダブル フロート c_longdouble ロングダブル フロート c_char_p char * (NULで終わる) バイト列オブジェクトまたは None c_wchar_p wchar_t * (NULで終了) 文字列またはなし c_void_p ボイド int または None上の表によると、C言語のlong long型に対応するctype型はc_longlongで、pythonの型はintであることがわかりますね。
pythonのデフォルトの値の取り扱い型はLong(8バイト)であり、宣言されていないgo言語でコンパイルされたrunメソッドはInt型(4バイト)の値を返すので、計算結果がIntの格納可能範囲を超えた場合に問題が発生します。
Int の範囲は -2^31 - 2^31-1 で、-2147483648 - 2147483647 となります。
あとは、実際のctypeに基づいて、pythonでrunの実際の戻り値の型を宣言するだけです。
from ctypes import *
import time
if __name__ == '__main__':
beginTime = time.time()
s = CDLL("s1.so") # load s1.so file
# According to the table, the corresponding ctypes for long long in C are c_longlonglong
s.run.restype = c_longlong # Declare the return value type of the run function of .so, fixed format
result = s.run(100000000) # Call the run function in the .so file generated by Go
print(result)
endTime = time.time()
print("elapsed time:", endTime - beginTime)
<イグ
結果は、もう文句なしです。
戻り値が文字列の場合の処理
s2.goのコードです。
package main
import (
"C" //C must be imported
)
//export speak
func speak(n int) string {
return "996 so tired ah, a rare day off, take a good rest "
}
func main() {
//main function do not write anything, and package name main to correspond to
}
s2.hを表示します。
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
typedef _GoString_ GoString;
...
extern GoString speak(GoInt n);
...
上記から、GoStringが
_GoString_
型であるのに対し
_GoString_
は、char * と ptrdiff_t の構造体です。
c言語仕様では、ptrdiff_tはマシン依存の データ型 ptrdiff_t 型は 変数 は通常、2つの ポインタ ptrdiff_t はファイル stddef.h (cstddef) で定義されています。ptrdiff_t は通常 long int 型として定義されますが、long long 型として定義することもできます。
表を見ると、構造体c_char_pとc_longlongはpythonで宣言する必要があることがわかります。
class GoString(Structure):
# typedef struct { const char *p; ptrdiff_t n; } _GoString_;
# ptrdiff_t == long long
_fields_ = [("p", c_char_p), ("n", c_longlong)]
s3.pyの完全なコードです。
from ctypes import *
import time
class GoString(Structure):
# typedef struct { const char *p; ptrdiff_t n; } _GoString_;
_fields_ = [("p", c_char_p), ("n", c_longlong)]
if __name__ == '__main__':
beginTime = time.time()
s = CDLL("s2.so") # load s1.so file
s.speak.restype = GoString
speakStr = s.speak(5)
# return is byte type, need to convert to string, the return content in .p, .n is the length of the cut
speakStr = speakStr.p[:speakStr.n].decode("utf-8")
print("speak:", speakStr)
endTime = time.time()
print("elapsed time:", endTime - beginTime)
<イグ
しかし、この上のコードは返された文字列を定数としてしかサポートしておらず、一度goのコードを以下のように変更して、上記の手順を繰り返しています。
s2.goのコードです。
package main
import (
"C" //C must be imported
"strconv"
)
//export speak
func speak(n int) string {
s := "996 so tired ah, a rare day off, rest " + strconv.Itoa(n)
return s
}
func main() {
//main function do not write anything, and package name main to correspond
}
上記の手順を繰り返して s3.py と表示され、以下のエラーが発生します。
これは、この時点の文字列がcオブジェクトではなく、goオブジェクトであるためで、以下のコードに修正することができます。
s2.goのコードです。
package main
import (
"C" //C must be imported
"strconv"
)
//export speak
func speak(n int) *C.char {
s := "996 so tired ah, a rare day off, take a good rest " + strconv.Itoa(n)
return C.CString(s)
}
func main() {
//main function in what do not write, and package name main to correspond to
}
上記のコードは、cで文字列型を返すと宣言していますが、.hファイルを見ると、以下のようになります。
extern char* speak(GoInt n);
次に s3.pyのコードは、単に次のように変更する必要があります。
from ctypes import *
import time
if __name__ == '__main__':
beginTime = time.time()
s = CDLL("s3.so") # load s1.so file
s.speak.restype = c_char_p
speakStr = s.speak(7).decode("utf-8")
print("speak:", speakStr)
endTime = time.time()
print("elapsed time:", endTime - beginTime)
<イグ
スムーズに動作する
ctypesを使ったC言語のコードへのアクセス
基本的な例
2つの数値の和を実装したC言語コード add.c ドキュメントをご覧ください。
#include <stdio.h>
int add_int(int, int);
float add_float(float, float);
int add_int(int num1, int num2){
return num1 + num2;
}
float add_float(float num1, float num2){
return num1 + num2;
}
として、Cファイルをコンパイルします。
.so
ファイルを作成します。
#For Linux or windows
gcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c
#For Mac
gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c
Pythonのコードで呼び出す場合。
from ctypes import *
adder = CDLL('adder.so')
res_int = adder.add_int(4, 5)
print("Sum of 4 and 5 = " + str(res_int))
add_float = adder.add_float
add_float.restype = c_float
a = c_float(5.5)
b = c_float(4.1)
print("Sum of 5.5 and 4.1 = ", str(add_float(a, b)))
出力します。
Sum of 4 and 5 = 9
Sum of 5.5 and 4.1 = 9.600000381469727
ctypes インターフェースは、ネイティブの Python でデフォルトの文字列や整数の型を使用する C 関数を引数として呼び出すことを可能にしますが、boolean や浮動小数点などの他の型については、正しい ctype 型を使用しなければ実行できません。例えば、呼び出す先が
adder.add_float()
関数に渡す場合、Pythonのfloat型をc_floatに変換してからC関数に渡さなければなりません。
複雑な例
というコーディングのcコード sample.cファイル を読み取ることができます。
#include
The following code is executed on the command line, compiling c.
gcc -shared -o sample.so sample.c
Write the python code in the same directory as the sample.so file.
sample.py file
import ctypes
_mod = ctypes.cdll.LoadLibrary('sample.so')
# int gcd(int, int)
gcd = _mod.gcd
gcd.argtypes = (ctypes.c_int, ctypes.c_int)
gcd.restype = ctypes.c_int
# int in_mandel(double, double, int)
in_mandel = _mod.in_mandel
in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int)
in_mandel.restype = ctypes.c_int
# int divide(int, int, int *)
_divide = _mod.divide
_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))
_divide.restype = ctypes.c_int
def divide(x, y):
rem = ctypes.c_int()
quot = _divide(x, y, rem)
return quot, rem.value
# void avg(double *a, int n)
# Define the type of the 'double *' parameter
class DoubleArrayType:
def from_param(self, param):
typename = type(param). __name__
if hasattr(self, 'from_' + typename):
return getattr(self, 'from_' + typename)(param)
elif isinstance(param, ctypes.Array):
return param
else:
raise TypeError("Can't convert %s" % typename)
# Cast from array.array objects
def from_array(self, param):
if param.typecode ! = 'd':
raise TypeError('must be an array of doubles')
ptr, _ = param.buffer_info()
return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))
# Cast from lists/tuples
def from_list(self, param):
val = ((ctypes.c_double) * len(param))(*param)
return val
from_tuple = from_list
# Cast from a numpy array
def from_ndarray(self, param):
return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
_avg = _mod.avg
_avg.argtypes = (DoubleArrayType(), ctypes.c_int)
_avg.restype = ctypes.c_double
def avg(values):
return _avg(values, len(values))
# struct Point { }
class Point(ctypes.Structure):
_fields_ = [('x', ctypes.c_double),
('y', ctypes.c_double)]
# double distance(Point *, Point *)
distance = _mod.distance
distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point))
distance.restype = ctypes.c_double
Then it's time to load and use the C functions defined inside, writing
test.py
:
import sample
print("sample.gcd(35, 42):", sample.gcd(35, 42))
print("sample.in_mandel(0, 0, 500):", sample.in_mandel(0, 0, 500))
print("sample.in_mandel(2.0, 1.0, 500):", sample.in_mandel(2.0, 1.0, 500))
print("sample.divide(42, 8):", sample.divide(42, 8))
print("sample.avg([1, 2, 3]):", sample.avg([1, 2, 3]))
p1 = sample.Point(1, 2)
p2 = sample.Point(4, 5)
print("sample.distance(p1, p2):", sample.avg([1, 2, 3]))
Result of execution.
sample.gcd(35, 42): 7
sample.in_mandel(0, 0, 500): 1
sample.in_mandel(2.0, 1.0, 500): 0
sample.divide(42, 8): (5, 2)
sample.avg([1, 2, 3]): 2.0
sample.distance(p1, p2): 2.0
Explanation of complex examples
Loading the c function library
If the C library is installed as a standard library, then the ctypes.util.find_library() function can be used to find where it is located: the
>>> from ctypes.util import find_library
>>> find_library('m')
'libm.so.6'
>>> find_library('pthread')
'libpthread.so.0'
>>> find_library('sample')
If it is a non-standard library, you need to know where the C library is located and then use ctypes.cdll.LoadLibrary() to load it.
_mod = ctypes.cdll.LoadLibrary(_path) #_path is the location of the C library, both full and relative paths are possible
Specify the type of parameters and return values
After the function libraries are loaded, specific symbols need to be extracted to specify their types. For example
# int in_mandel(double, double, int)
in_mandel = _mod.in_mandel
in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int)
in_mandel.restype = ctypes.c_int
In this code, the function's
.argtypes
attribute is a tuple containing the input arguments to a function, and the
.restype
is the return type of the function.
The ctypes defined by c_double, c_int, c_short, c_float, etc. represent the corresponding C data types.
The binding of these type signatures is an important step in order for Python to pass the correct argument types and convert the data correctly. Omitting this type-signature step can cause the code to not run properly, or even hang the entire interpreter process.
The pointer argument needs to be passed in as a ctypes object
Native C code types sometimes don't correspond explicitly to Python, e.g.
# int divide(int, int, int *) in c code
_divide = _mod.divide
_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))
_divide.restype = ctypes.c_int
# calls in python code
x = 0
divide(10, 3, x)
This way of writing violates Python's immutability principle for integers and can cause the entire interpreter to fall into a black hole.
For arguments involving pointers, it is usually necessary to construct a corresponding ctypes object before passing it in as an argument: the
x = ctypes.c_int()
divide(10, 3, x)
The ctypes.c_int instance is passed in as a pointer, and unlike normal Python integers, the c_int object can be modified.
.value
attribute can be used to get or change this value: the
x.value
For such un-Python-like C calls, it is often possible to write a wrapper function that
# int divide(int, int, int *)
_divide = _mod.divide
_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))
_divide.restype = ctypes.c_int
def divide(x, y):
rem = ctypes.c_int()
quot = _divide(x, y, rem)
return quot, rem.value
Arguments contain arrays
For the avg() function, the
double avg(double *a, int n)
, the C code expects to receive a pointer to an array of type double and an array length value.
Arrays come in many forms in Python, including lists, tuples, arrays of array modules, numpy arrays, and more.
DoubleArrayType demonstrates how to handle this case.
The method from_param() takes a single argument and then converts it down to a suitable ctypes object.
def from_param(self, param):
typename = type(param). __name__
if hasattr(self, 'from_' + typename):
return getattr(self, 'from_' + typename)(param)
elif isinstance(param, ctypes.Array):
return param
else:
raise TypeError("Can't convert %s" % typename)
The type name of the argument is extracted and used to distribute it to a more specific method.
For example, if the argument is a list, then typename is list, and the from_list method is called.
def from_list(self, param):
val = ((ctypes.c_double) * len(param))(*param)
return val
Demonstrating the conversion of list lists to ctypes arrays via the interactive command line.
>>> import ctypes
>>> nums = [1, 2, 3]
>>> a = (ctypes.c_double * len(nums))(*nums)
>>> a
<__main__.c_double_Array_3 object at 0x10069cd40>
>>> a[0]
1.0
>>> a[1]
2.0
>>> a[2]
3.0
If the argument is a numpy array, then typename is ndarray, and the from_ndarray method is called.
def from_ndarray(self, param):
return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
If the argument is an array object, then typename is array, and the from_array method is called.
def from_array(self, param):
if param.typecode ! = 'd':
raise TypeError('must be an array of doubles')
ptr, _ = param.buffer_info()
return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))
For array objects, the buffer_info() method gets the corresponding memory address and length of the array, and ctypes.cast() converts the memory address to a ctypes pointer object.
>>> import array
>>> a = array.array('d',[1,2,3])
>>> a
array('d', [1.0, 2.0, 3.0])
>>> ptr,length = a.buffer_info()
>>> ptr
4298687200
>>> length
3
>>> ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))
<__main__.LP_c_double object at 0x10069cd40>
By defining the DoubleArrayType class and using it in the avg() type signature, then this function can accept multiple different array-like inputs.
import sample
sample.avg([1,2,3])
2.0
sample.avg((1,2,3))
2.0
import array
sample.avg(array.array('d',[1,2,3]))
2.0
import numpy
sample.avg(numpy.array([1.0,2.0,3.0]))
2.0
The parameters contain the structure
For structures, it is enough to simply define a class containing the appropriate fields and types: the
class Point(ctypes.Structure):
_fields_ = [('x', ctypes.c_double),
('y', ctypes.c_double)]
Type signature bindings require only.
# double distance(Point *, Point *)
distance = _mod.distance
distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point))
distance.restype = ctypes.c_double
Once a class is defined, it can be used in a type signature or in code that needs to instantiate a structure. For example.
>>> p1 = sample.Point(1,2)
>>> p2 = sample.Point(4,5)
>>> p1.x
1.0
>>> p1.y
2.0
>>> sample.distance(p1,p2)
4.242640687119285
Converting function pointers to callable objects
Get the memory address of a C function (tested and supported on linux, not on windows).
import ctypes
lib = ctypes.cdll.LoadLibrary(None)
# Get the address of the sin() function of the C math library
addr = ctypes.cast(lib.sin, ctypes.c_void_p).value
print(addr)
The above code gets the integer 140266666308000 under linux, while under Windows it reports an error
TypeError: LoadLibrary() argument 1 must be str, not None
With the memory address of the function, it can be converted into a Python callable object.
# Convert the function address into a Python callable object with arguments of the function's return value type and argument type
functype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double)
sin = functype(addr)
print(sin)
The first argument to CFUNCTYPE() is the return type, the next argument is the argument type, and the resulting object is used as a normal function accessible via ctypes.
Print: <CFunctionType object at 0x7f9261becb38>
Calling the test.
>>> import math
>>> math.pi
3.141592653589793
>>> sin(math.pi)
1.2246467991473532e-16
>>> sin(math.pi/2)
1.0
>>> sin(math.pi/6)
0.499999999999999999994
>>> sin(2)
0.9092974268256817
>>> sin(0)
0.0
The techniques involved here are widely used in various advanced code generation techniques, such as on-the-fly compilation, as seen in the LLVM function library.
The following is a brief demonstration of the llvmpy extension, which builds a small aggregate function, gets its function pointer, and then converts it to a Python callable object and executes the function.
>>> from llvm.core import Module, Function, Type, Builder
>>> mod = Module.new('example')
>>> f = Function.new(mod, Type.function(Type.double(), [Type.double(), Type.double()], False), 'foo')
>>> block = f.append_basic_block('entry')
>>> builder = Builder.new(block)
>>> x2 = builder.fmul(f.args[0],f.args[0])
>>> y2 = builder.fmul(f.args[1],f.args[1])
>>> r = builder.fadd(x2,y2)
>>> builder.ret(r)
<llvm.core.Instruction object at 0x10078e990>
>>> from llvm.ee import ExecutionEngine
>>> engine = ExecutionEngine.new(mod)
>>> ptr = engine.get_pointer_to_function(f)
>>> ptr
4325863440
>>> foo = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double, ctypes.c_double)(ptr)
>>> foo(2,3)
13.0
>>> foo(4,5)
41.0
>>> foo(1,2)
5.0
Note: This is dealing directly with machine-level memory addresses and local machine code, not Python functions.
Handling the case where the argument contains a string
Testing the program
str1.c
#include
Result of execution.
> gcc str1.c&a.exe
Hello
48 65 6c 6c 6f
Compile the c program into a so file.
gcc -shared -o str1.so str1.c
Called with python.
import ctypes
_mod = ctypes.cdll.LoadLibrary('str1.so')
# void print_chars(char *s)
print_chars = _mod.print_chars
print_chars.argtypes = (ctypes.c_char_p,)
print_chars(b'Hello')
print_chars(b'Hello\x00World')
Print the result.
Hello
48 65 6c 6c 6f
Hello
48 65 6c 6c 6f
You cannot pass in python string types directly, e.g.
print_chars('Hello World')
Otherwise, an error is reported as
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
If you need to pass a string instead of bytes, you can first encode it in UTF-8 to bytes as follows
>>> print_chars('Hello World'.encode('utf-8'))
Hello World
48 65 6c 6c 6f 20 57 6f 72 6c 64
gcc -shared -o sample.so sample.c
関連
-
PythonによるExcelファイルの一括操作の説明
-
[解決済み】Python - "ValueError: not enough values to unpack (expected 2, got 1)" の修正方法 [閉店].
-
[解決済み】Python 2.7 : LookupError: unknown encoding: cp65001 [重複]。
-
[解決済み] TypeError: タプルにしか連結できない("str "ではない) Error
-
[解決済み] 辞書のスライス
-
[解決済み] ValueError: シグナルはメインスレッドでのみ動作します。
-
[解決済み] TypeError: NoneTypeではなく、strでなければならない。
-
[解決済み] Flaskのcssが更新されない【非公開
-
[解決済み] ImportError: Cython'という名前のモジュールがない [重複] 。
-
[解決済み] PhantomJS with Seleniumのエラーです。メッセージ 'phantomjs' 実行ファイルが PATH にある必要があります。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
python call matlab メソッドの詳細
-
[解決済み】ValueError: 手動フィールド指定から自動フィールド番号指定に切り替えることができない
-
Django のシリアライズの具体的な使用方法
-
[解決済み】Numpyのstd計算。TypeError: cannot perform reduce with flexible type.
-
[解決済み] SQLAlchemy でテーブルを削除する方法は?
-
[解決済み] Pythonで現在のモジュール内のすべてのクラスのリストを取得するにはどうすればよいですか?
-
[解決済み] Python: urllib.error.HTTPError: HTTP Error 404: 見つかりません
-
[解決済み] pkg_resources という名前のモジュールがない
-
redis' という名前のモジュールがない
-
Python 3.6でscipyという名前のモジュールがない場合の解決策