精华内容
下载资源
问答
  • python调用go或c语言

    2021-01-01 09:12:27
    python调用go语言 ​ Python是一个生产力很高的语言,能够以最高的效率完成最多的事,但是Python的性能,是我们一直诟病的一个问题,尤其是一个大锁GIL。当然现在大部分程序都是(IO)网络密集型程序,Python足以...

    本文档由小小明个人学习整理

    文章链接:https://blog.csdn.net/as604049322/article/details/112058313

    pdf下载地址:https://download.csdn.net/download/as604049322/13999212

    python调用go语言

    ​ Python是一个生产力很高的语言,能够以最高的效率完成最多的事,但是Python的性能,是我们一直诟病的一个问题,尤其是一个大锁GIL。当然现在大部分程序都是(IO)网络密集型程序,Python足以胜任,但是如果说我们已经存在的项目或者想要开发的项目中,存在有计算密集型的程序场景,该如何提升性能呢?

    ​ 一般是可以用C\C++重写Python计算密集的地方,来提高性能,但是C\C++是有一些学习成本的,指针和自己释放内存都有一定门槛。Go就很方便了,自动垃圾自动回收,还有天生高并发等优势。

    ​ python的ctypes模块提供了和C语言兼容的数据类型和函数来加载so/dll动态链接库文件,而GO语言本身就可以编译出符合c语言规范的dll或so动态链接库,基于这两项特性,于是我们可以顺利的使用python来调用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上部署,在处理第三方依赖是比较麻烦,不仅开发累,运维也累,虽然现在有docker解决了这个痛点,但是应该还是没原生来的舒服。

    如果使用Go的话,不管是什么第三方依赖,最终只会打包成一个可执行文件,直接部署即刻,并且是高并发方式,心再大一点,连Nginx都不用,但是一点不用担心并发问题。

    示例

    Windows下编译linux平台可执行程序:

    cmd下依次执行以下命令:

    SET CGO_ENABLED=0  // 禁用CGO
    SET GOOS=linux  // 目标平台是linux
    SET GOARCH=amd64  // 目标处理器架构是amd64
    

    然后执行go build,得到的就是能够在linux上的可执行文件。

    将这个文件上传到linux服务器上,即使Go环境都没有,都可以执行成功。

    Windows下编译Mac平台64位可执行程序:

    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
    

    Linux 下编译 Mac 和 Windows 平台64位可执行程序:

    CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build
    CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
    

    python与go性能对比

    为了更好的体现出来优化之后的效果,我们大概对比一下两个语言在计算密集情况下的差距。

    测试:分别计算一个亿(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("耗时:", endTime - startTime)
    

    耗时5s左右:

    image-20200926224048065

    Go代码:

    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("耗时:", time.Since(startTime))
    }
    

    耗时50ms左右:

    image-20200926224400517

    Go代码编译为Python可调用的.so文件

    安装64位gcc工具MinGW

    去https://sourceforge.net/projects/mingw-w64/下载后,一步步安装

    已经将离线包上传到了百度云:

    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_64-w64-mingw32-static/include' CPPFLAGS=' -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' LDFLAGS='-pipe -fno-ident -L/c/mingw810/x86_64-810-win32-seh-rt_v6-rev0/mingw64/opt/lib -L/c/mingw810/prerequisites/x86_64-zlib-static/lib -L/c/mingw810/prerequisites/x86_64-w64-mingw32-static/lib '
    Thread model: win32
    gcc version 8.1.0 (x86_64-win32-seh-rev0, Built by MinGW-W64 project)
    

    需要被编译.so文件的go代码有些要求,例如必须导入C:

    package main
    
    import (
      "C" //C必须导入
    )
    
    //export run
    func run(n int) int{
      // 必须通过export 函数名格式的注释申明该函数可以被外部接口
      sum := 0
      for i := 0; i < n; i++ {
        sum += i
      }
      fmt.Println("我是Go代码,我跑完了,我的结果是:",sum)
      return sum
    }
    
    func main() {
      //main函数中什么都不要写,和包名main要对应
    }
    

    编译为.so文件供Python调用:

    go build -buildmode=c-shared -o s1.so s1.go
    

    格式:go build -buildmode=c-shared -o 输出的.so文件 go源文件

    会生成.h文件和.so文件,.so文件供Python调用,如下图所示:

    image-20200927002106002

    Ptyhon调用so文件

    将上述生成的.so文件复制到Python项目的同一级目录。

    编写s1.py,依然是计算一个亿,关键部分由Go生成的.so执行:

    from ctypes import *
    import time
    
    if __name__ == '__main__':
        startTime = time.time()
    
        s = CDLL("s1.so")  # 加载s1.so文件
        result = s.run(100000000)  # 调用Go生成的.so文件里面的run函数
        print("result:", result)
    
        endTime = time.time()
        print("耗时:", endTime - startTime)
    

    共耗时:0.04s左右:

    image-20200927003245302

    可以看到,虽然速度很快,但是Python在调用Go生成的.so文件之后,拿到的返回值竟然是错的,但是在Go中打印的确实对的!

    但是计算一些的比较小的数,以10023为例,结果是正确的:

    image-20200927003538178

    .h文件探究

    上面的问题是因为默认返回值类型存储范围有限导致的,下面将具体分析go编译生成的c中间文件一探究竟。

    打开.h文件,翻到末尾:

    image-20200927005445020

    找到extern开头的声明:

    extern GoInt run(GoInt n);
    

    这是前面go源码中声明的run方法被转换为c语言代码,表示参数和返回值类型在c语言中都是GoInt类型。

    翻到类型定义的位置:

    image-20200927010139762

    可以看到,GoInt其实就是GoInt64,GoInt64的类型是long long类型。

    Python使用ctypes模块调用.so文件时有一个对应表:

    参考:https://docs.python.org/zh-tw/3.7/library/ctypes.html

    ctypes 类型 C 类型 Python 类型
    c_bool _Bool bool (1)
    c_char char 单字符字节对象
    c_wchar wchar_t 单字符字符串
    c_byte char int
    c_ubyte unsigned char int
    c_short short int
    c_ushort unsigned short int
    c_int int int
    c_uint unsigned int int
    c_long long int
    c_ulong unsigned long int
    c_longlong __int64 或 long long int
    c_ulonglong unsigned __int64 或 unsigned long long int
    c_size_t size_t int
    c_ssize_t ssize_t 或 Py_ssize_t int
    c_float float float
    c_double double float
    c_longdouble long double float
    c_char_p char * (以 NUL 结尾) 字节串对象或 None
    c_wchar_p wchar_t * (以 NUL 结尾) 字符串或 None
    c_void_p void * 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")  # 加载s1.so文件
        # 根据查表,C中的long long,对应的ctypes 是 c_longlong
        s.run.restype = c_longlong  # 声明.so的run函数返回值类型,固定格式
        result = s.run(100000000)  # 调用Go生成的.so文件里面的run函数
    
        print(result)
        endTime = time.time()
        print("耗时:", endTime - beginTime)
    

    image-20200927013751047

    现在结果就没有问题了。

    处理返回值为字符串的情况

    s2.go的代码:

    package main
    
    import (
    	"C" //C必须导入
    )
    
    //export speak
    func speak(n int) string {
    	return "996好累呀,难得休息一天,好好休息 "
    }
    func main() {
    	//main函数中什么都不要写,和包名main要对应
    }
    

    查看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是C/C++标准库中定义的一个与机器相关的数据类型。ptrdiff_t类型变量通常用来保存两个指针减法操作的结果。ptrdiff_t定义在stddef.h(cstddef)这个文件内。ptrdiff_t通常被定义为long int类型,可以被定义为long long类型。

    查表可知,在python中应申明c_char_p和c_longlong的结构体:

    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")  # 加载s1.so文件
    
        s.speak.restype = GoString
        speakStr = s.speak(5)
        # 返回的是字节类型,需要转字符串,返回的内容在.p中,.n是切的长度
        speakStr = speakStr.p[:speakStr.n].decode("utf-8")
        print("speak:", speakStr)
    
        endTime = time.time()
        print("耗时:", endTime - beginTime)
    

    image-20200927024920472

    但是上面的这种代码只支持返回的字符串为常量,一旦我将go代码修改为以下内容再重复以上步骤时:

    s2.go代码:

    package main
    
    import (
    	"C" //C必须导入
    	"strconv"
    )
    
    //export speak
    func speak(n int) string {
    	s := "996好累呀,难得休息一天,好好休息 " + strconv.Itoa(n)
    	return s
    }
    
    func main() {
    	//main函数中什么都不要写,和包名main要对应
    }
    

    重复以上步骤,运行s3.py,出现如下错误:

    image-20200927031344190

    这是因为此时的string不是c语言的对象,而是go语言的对象,修改为如下代码即可:

    s2.go代码:

    package main
    
    import (
    	"C" //C必须导入
    	"strconv"
    )
    
    //export speak
    func speak(n int) *C.char {
    	s := "996好累呀,难得休息一天,好好休息 " + strconv.Itoa(n)
    	return C.CString(s)
    }
    
    func main() {
    	//main函数中什么都不要写,和包名main要对应
    }
    

    以上代码申明返回c语言的字符串类型,查看.h文件可以看到:

    extern char* speak(GoInt n);
    

    那么s3.py代码只需修改为:

    from ctypes import *
    import time
    
    if __name__ == '__main__':
        beginTime = time.time()
        s = CDLL("s3.so")  # 加载s1.so文件
    
        s.speak.restype = c_char_p
        speakStr = s.speak(7).decode("utf-8")
        print("speak:", speakStr)
    
        endTime = time.time()
        print("耗时:", endTime - beginTime)
    

    image-20200927031932312

    顺利运行。

    使用ctypes访问C代码

    基本示例

    实现两数求和的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接口允许我们在调用C函数时参数使用原生Python中默认的字符串型和整型,而对于其他类似布尔型和浮点型这样的类型,必须要使用正确的ctype类型才可以。如向adder.add_float()函数传参时, 要先将Python中的float类型转化为c_float类型,然后才能传送给C函数。

    复杂示例

    编码c代码,sample.c文件的内容为:

    #include <math.h>
    
    int gcd(int x, int y) {
        int g = y;
        while (x > 0) {
            g = x;
            x = y % x;
            y = g;
        }
        return g;
    }
    
    int in_mandel(double x0, double y0, int n) {
        double x = 0, y = 0, xtemp;
        while (n > 0) {
            xtemp = x * x - y * y + x0;
            y = 2 * x * y + y0;
            x = xtemp;
            n -= 1;
            if (x * x + y * y > 4) return 0;
        }
        return 1;
    }
    
    int divide(int a, int b, int *remainder) {
        int quot = a / b;
        *remainder = a % b;
        return quot;
    }
    
    double avg(double *a, int n) {
        int i;
        double total = 0.0;
        for (i = 0; i < n; i++) {
            total += a[i];
        }
        return total / n;
    }
    
    typedef struct Point {
        double x, y;
    } Point;
    
    double distance(Point *p1, Point *p2) {
        return hypot(p1->x - p2->x, p1->y - p2->y);
    }
    

    命令行执行以下代码,编译c:

    gcc -shared -o sample.so sample.c
    

    在 sample.so 所在文件相同的目录编写python代码,sample.py文件

    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)
    # 定义 'double *'参数的类型
    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
    

    然后就可以加载并使用里面定义的C函数了,编写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]))
    

    执行结果:

    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
    

    复杂示例解析

    加载c函数库

    如果C函数库被安装为一个标准库,那么可以使用 ctypes.util.find_library() 函数来查找它所在的位置:

    >>> from ctypes.util import find_library
    >>> find_library('m')
    'libm.so.6'
    >>> find_library('pthread')
    'libpthread.so.0'
    >>> find_library('sample')
    

    如果是非标准库,则需要知道C函数库的位置,然后使用 ctypes.cdll.LoadLibrary() 来加载它:

    _mod = ctypes.cdll.LoadLibrary(_path) #_path是C函数库的位置,全路径和相对路径都可以
    

    指定参数和返回值的类型

    函数库被加载后,需要提取特定的符号指定它们的类型。例如:

    # 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
    

    这段代码中,函数的.argtypes 属性是一个元组,包含了某个函数的输入参数,而 .restype 是函数的返回类型。

    ctypes 定义的c_double, c_int, c_short, c_float等代表了对应的C数据类型。

    为了让Python能够传递正确的参数类型并且正确的转换数据,这些类型签名的绑定是很重要的一步。如果省略这个类型签名的步骤,可能导致代码不能正常运行,甚至整个解释器进程挂掉。

    指针参数需要以ctypes对象形式传入

    原生的C代码的类型有时跟Python不能明确的对应上来,例如:

    # c代码中的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
    # python代码中的调用
    x = 0
    divide(10, 3, x)
    

    这种写法违反了Python对于整数的不可更改原则,并且可能会导致整个解释器陷入一个黑洞中。

    对于涉及到指针的参数,通常需要先构建一个相应的ctypes对象再作为参数传入:

    x = ctypes.c_int()
    divide(10, 3, x)
    

    ctypes.c_int 实例是作为指针被传进去的,跟普通Python整数不同的是,c_int 对象是可以被修改的。

    .value属性可被用来获取或更改这个值:

    x.value
    

    对于这种不像Python的C调用,通常可以写一个包装函数:

    # 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
    

    参数包含数组

    对于avg()函数,double avg(double *a, int n),C代码期望接受到一个double类型的数组指针和一个数组的长度值。

    在Python中数组有多种形式,包括列表、元组、array 模块的数组、 numpy 数组等。

    DoubleArrayType 演示了怎样处理这种情况。

    方法 from_param() 接受一个单个参数然后将其向下转换为一个合适的ctypes对象:

    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)
    

    参数的类型名被提取出来并被用于分发到一个更具体的方法中去。

    例如,如果参数是一个列表,那么 typename 就是 list ,然后 from_list 方法就会被调用。

    def from_list(self, param):
        val = ((ctypes.c_double) * len(param))(*param)
        return val
    

    演示通过交互式命令行将list列表转换为 ctypes 数组:

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

    如果参数是一个numpy数组,那么 typename 就是 ndarray,然后 from_ndarray方法就会被调用:

    def from_ndarray(self, param):
    	return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
    

    如果参数是一个数组对象,那么 typename 就是 array,然后 from_array方法就会被调用:

    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))
    

    对于数组对象,buffer_info()方法可以获取到数组对应的内存地址和长度,ctypes.cast()可以将内存地址转换为ctypes 指针对象:

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

    通过定义 DoubleArrayType类并在 avg() 类型签名中使用它,那么这个函数就能接受多个不同的类数组输入了:

    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
    

    参数包含结构体

    对于结构体,只需要简单的定义一个类,包含相应的字段和类型即可:

    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
    

    一旦类被定义后,就可以在类型签名中或者是需要实例化结构体的代码中使用它。例如:

    >>> p1 = sample.Point(1,2)
    >>> p2 = sample.Point(4,5)
    >>> p1.x
    1.0
    >>> p1.y
    2.0
    >>> sample.distance(p1,p2)
    4.242640687119285
    

    将函数指针转换为可调用对象

    获取C函数的内存地址(经测试,在linux上支持,windows上不支持):

    import ctypes
    lib = ctypes.cdll.LoadLibrary(None)
    # 获取C语言math库的sin()函数的地址
    addr = ctypes.cast(lib.sin, ctypes.c_void_p).value
    print(addr)
    

    上述代码在linux下得到整数140266666308000,而在Windows下会报错TypeError: LoadLibrary() argument 1 must be str, not None

    有了函数的内存地址,就可以将它转换成一个Python可调用对象:

    # 将函数地址转换成一个Python的可调用对象,参数为函数的返回值类型和参数类型
    functype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double)
    sin = functype(addr)
    print(sin)
    

    CFUNCTYPE() 的第一个参数是返回类型,接下来的参数是参数类型,生成的对象被当做普通的可通过 ctypes 访问的函数来使用。

    打印:<CFunctionType object at 0x7f9261becb38>

    调用测试:

    >>> import math
    >>> math.pi
    3.141592653589793
    >>> sin(math.pi)
    1.2246467991473532e-16
    >>> sin(math.pi/2)
    1.0
    >>> sin(math.pi/6)
    0.49999999999999994
    >>> sin(2)
    0.9092974268256817
    >>> sin(0)
    0.0
    

    这里面涉及的技术被广泛使用于各种高级代码生成技术,比如即时编译,在LLVM函数库中可以看到。

    下面简单演示下 llvmpy 扩展,构建一个小的聚集函数,获取它的函数指针,然后转换为一个Python可调用对象,并执行函数:

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

    注意:这是在直接跟机器级别的内存地址和本地机器码打交道,而不是Python函数。

    处理参数包含字符串的情况

    测试程序str1.c

    #include <stdio.h>
    
    void print_chars(char *s) {
        printf("%s\n",s);
        while (*s) {
            printf("%2x ", (unsigned char) *s);
            s++;
        }
        printf("\n");
    }
    
    int main() {
        print_chars("Hello");
    }
    

    执行结果:

    > gcc str1.c&a.exe
    Hello
    48 65 6c 6c 6f
    

    编译c程序为so文件:

    gcc -shared -o str1.so str1.c
    

    用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')
    

    打印结果:

    Hello
    48 65 6c 6c 6f 
    Hello
    48 65 6c 6c 6f 
    

    不能直接传入python的字符串类型,例如:print_chars('Hello World')

    否则会报错:ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type

    如果需要传递字符串而不是字节,可以先编码成 UTF-8 转成字节:

    >>> print_chars('Hello World'.encode('utf-8'))
    Hello World
    48 65 6c 6c 6f 20 57 6f 72 6c 64 
    
    展开全文
  • python调用go或c语言.pdf

    2021-01-01 09:17:52
    Python是一个生产力很高的语言,能够以最高的效率完成最多的事,但是Python的性能,是我们一直诟病的一个问题,尤其是一个大锁GIL。当然现在大部分程序都是(IO)网络密集型程序,Python足以胜任,但是如果说我们...
  • 下面这个个实例是使用jsonrpc实现给其他的语言提供rpc调用: maste.go package main import ( "errors" "log" "net" "net/rpc" "net/rpc/jsonrpc" ) func main() { lis, err := net.Listen("tcp", ":...
    package main
    
    import (
    	"errors"
    	"fmt"
    	"net/http"
    	"net/rpc"
    )
    
    func s_main() {
    	rpc.Register(new(Remote))
    	rpc.HandleHTTP()
    	http.ListenAndServe(":1789", nil)
    }
    func main() {
    	args := Test{"di", 24, []string{"basketball", "buitifulgirl"}}
    	client, err := rpc.DialHTTP("tcp", "127.0.0.1:1789")
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    	var b bool
    	err = client.Call("Remote.GetInfo", args, &b)
    	if err != nil {
    		fmt.Println(err)
    	}
    	var s string
    	err = client.Call("Remote.GetName", "WaCao", &s)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Println(b, s)
    }
    
    type Remote int32
    
    type Test struct {
    	Name  string
    	Age   int
    	Hobby []string
    }
    
    func (x *Remote) GetName(args string, result *string) error {
    	if args != "" {
    		*result = args
    		return nil
    	}
    	return errors.New("Input Empty")
    }
    func (x *Remote) GetInfo(args Test, result *bool) error {
    	if len(args.Hobby) == 0 {
    		return errors.New("Hobby Is Empty")
    	}
    	fmt.Println(args)
    	*result = true
    	return nil
    }



    下面这个个实例是使用jsonrpc实现给其他的语言提供rpc调用:

    maste.go

    package main
    
    import (
    	"errors"
    	"log"
    	"net"
    	"net/rpc"
    	"net/rpc/jsonrpc"
    )
    
    func main() {
    	lis, err := net.Listen("tcp", ":1789")
    	if err != nil {
    		return
    	}
    	defer lis.Close()
    
    	srv := rpc.NewServer()
    	if err := srv.RegisterName("Json", new(Json)); err != nil {
    		return
    	}
    
    	for {
    		conn, err := lis.Accept()
    		if err != nil {
    			log.Fatalf("lis.Accept(): %v\n", err)
    			continue
    		}
    		go srv.ServeCodec(jsonrpc.NewServerCodec(conn))
    	}
    }
    
    type Json struct {
    	Name string `json:name`
    	Age  int    `json:age`
    }
    
    func (self *Json) Getname(args Json, result *Json) error {
    	if args.Name == "di" {
    		log.Println(args)
    		*result = Json{"Xichen", 24}
    		return nil
    	}
    	return errors.New("Input Name")
    }
    

    在这里使用python作为客户端来实现远程调用的:

    import json
    import socket
    import itertools
    import time
     
    class RPCClient(object):
     
        def __init__(self, addr, codec=json):
            self._socket = socket.create_connection(addr)
            self._id_iter = itertools.count()
            self._codec = codec
     
        def _message(self, name, *params):
            return dict(id=self._id_iter.next(),
                        params=list(params),
                        method=name)
     
        def call(self, name, *params):
            req = self._message(name, *params)
            id = req.get('id')
     
            mesg = self._codec.dumps(req)
            self._socket.sendall(mesg)
     
            # This will actually have to loop if resp is bigger
            resp = self._socket.recv(4096)
            resp = self._codec.loads(resp)
     
            if resp.get('id') != id:
                raise Exception("expected id=%s, received id=%s: %s"
                                %(id, resp.get('id'), resp.get('error')))
     
            if resp.get('error') is not None:
                raise Exception(resp.get('error'))
     
            return resp.get('result')
     
        def close(self):
            self._socket.close()
     
    
    if __name__ == '__main__':
        rpc = RPCClient(("127.0.0.1", 1789))
        mv = dict(name="di",age=24)
        print rpc.call("Json.Getname", mv)
    


    展开全文
  • 1、Go语言利用exec可以调用python脚本,并通过websocket把结果传送给前端。注意点1:如何实现cd到指定目录下运行python脚本,cmd.Dir = “E:\test”,进入python脚本所在目录。注意点2:python脚本必须与matlab的.m...

    1、Go语言利用exec可以调用python脚本,并通过websocket把结果传送给前端。注意点1:如何实现cd到指定目录下运行python脚本,cmd.Dir = “E:\test”,进入python脚本所在目录。注意点2:python脚本必须与matlab的.m文件在同一目录下。

            matlabPath := "E:\\test\\Analysis.m"
            cmd := exec.Command("python", "test1.py", dataPth, jsonPth, files, upDown, channel, dataSource, matlabPath)
            cmd.Dir = "E:\\test"
            cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
            err := cmd.Start()
            if err != nil {
                log.Error("file service.configure.go, func err2 :", err, ", cmd failed", "cmd :", cmd)
                return
            } else {
                errWait := cmd.Wait()
                if errWait == nil {
                    return
                } else {
                    pictureStringSlice := this.getPictureFile(filesPath)
                    err := websocket.JSON.Send(ws, pictureStringSlice)
                    if err != nil {
                        log.Error("err", err)
                    }
                }
            }

    2、python如何调用matlab,利用python的pymatbridge模块可以实现,并且可以并行调用matlab。若保存的图片缩小了,则添加mlab.set_plot_settings(width = 1200,height = 900,inline = True)设置图片大小。

    from pymatbridge import Matlab
    import sys
    
    if __name__ == '__main__':
        dat = sys.argv[1]
        json = sys.argv[2]
        picture = sys.argv[3]
        upDown = sys.argv[4]
        channel = sys.argv[5]
        dataSource = sys.argv[6]
        pathMat = sys.argv[7]
        print pathMat
        mlab = Matlab()
        mlab.start()
        mlab.set_plot_settings(width = 1200,height = 900,inline = True)
        res = mlab.run_func(pathMat,{'arg1':dat ,'arg2':json ,'arg3':picture ,'arg4':upDown,'arg5':channel,'arg6':dataSource})

    pathMat是matlab主程序所在目录E:\test\Analysis.m,arg1~arg6是传送给matlab的参数。
    3、matlab接收参数

    %% MATLAB
    function retVal = Analysis(args)
    strMatFileName = args.arg1;
    strJsonFileName = args.arg2;
    strOutputPath = args.arg3;
    strLink = args.arg4;
    strPhyChannel = args.arg5;
    strInputPoint = args.arg6;
    展开全文
  • go语言调用python问题

    2019-10-12 17:25:04
    1、Go语言调用python出错,直接cmd中运行python脚本没有问题,解决方法,Go语言用32位系统编译即可。

    1、Go语言调用python出错,直接cmd中运行python脚本没有问题,解决方法,Go语言用32位系统编译即可。

    展开全文
  • package main import "C" import "fmt" func main() { res := Count() fmt.Println(res) } //export Count func Count() int { i:=0 num:=0 for i上面是go语言的整形叠加函数, 下面是Python调用该go文件的动态链接库...
  • Go 语言调用 python2

    2019-03-21 10:59:25
    Go 是静态语言,性能很好,但是不那么灵活,不好在运行时动态运行代码。Python是动态语言,非常灵活,但是性能很差。古人云:“鱼和熊掌不能兼得”。但是如今有了Go-Python,鱼和熊掌也可以兼得。 注意:Go-Python ...
  • 常用的消息摘要算法有MD5和SHA,这些算法在pythongo的库中都有,需要时候调用下就OK了,这里总结下pythongo的实现。 一、python消息摘要示例 代码如下: 复制代码 代码如下: #! /usr/bin/python ”’  File : ...
  • go语言调用python

    千次阅读 2018-10-26 16:58:54
    https://github.com/google/grumpy go语言调用python 的github
  • 一、用python调用其它程序,并得到输出 示例代码: 复制代码 代码如下:import osvar = os.popen(‘ls -l’).read()print var 运行效果(以我机器为例): 二、用go语言调用其它程序,并得到输出 go代码: 复制代
  • 徒手使用pythongo语言搭建最简单的web页面-使用模板,无持久化 也许我们会接触到很多语言的web应用,譬如php,java,包括今天介绍的python和go,实际上我们在使用这些语言构建web应用的时候,很多时候变成了单纯...
  • python 程序中调用go

    2018-04-24 17:11:00
    那么Python程序需要一些计算量比较大的模块时一般会调用c或者c++的代码来重写,但是c/c++编写代码代价太高,耗费太多的人力,开发周期太长,那么就想到来一个折中的方法是用golang语言。 虽然golang性能比不上c、...
  • 2、由于没有找到Go语言直接把.dat转换为.mat格式的方法,采用Go语言调用python转换。利用os/exec包中的Command方法调用python,后面三个是参数,存放需要转换数据的路径及转换格式。 cmd := exec.Command("...
  • “ 阅读本文大概需要 3 分钟。 ”我们知道,在涉及到大量 CPU 计算的时候,Python 的运行效率可能不如其他语言。今天我们讲一下如何直接用 Python 调用 Go 语言写的代码...
  • 在写一个连连看外挂的时候,要在PHP文件中调用go语言来执行控制鼠标的操作,这涉及到了PHP调用外部文件,其解决办法是使用exec函数: shell_exec('C:\Go\bin\go.exe run ./index.go'); 过程中一开始只能打开index....
  • 我们知道,在涉及到大量 CPU...并且,我们这次不是做转换,而是直接用 Python 调用 Go 语言写的代码。 今天的文章内容需要使用 Linux 和 macOS 系统实现。或者你也可以使用 Windows 10自带的 WSL2 Linux子系统。 今天我
  • 它结合了功能强大的软件堆栈和代码生成引擎,以构建在C++,Java,GoPython,PHP,Ruby,Erlang,Perl,C#,Cocoa,JavaScript,Node.js,Smalltalk,and OCaml这些变成语言间无缝结合的。高效的服务。 thrift最初...
  • Python gRPC 与跨语言 gRPC 调用 本文是我上一篇文章《 Go 微服务基础:Protobuf & gRPC》的延伸,在开始本文前,建议先看一看那篇文章。 在《 Go 微服务基础:Protobuf & gRPC》一文中,我们介绍了 ...
  • def demo_input_and_output(): input = yield 'what is the input?' yield 'input is: %s' % input gen = demo_input_and_output() ...而且这个接收数据的操作是一个阻塞的操作,如果外部没有调用 next()
  • Python调用C/C++(使用SWIG)

    千次阅读 2017-09-13 15:59:49
    这里选用SWIG的一个重要原因是,它不仅可以用于Python,也可以用于其他语言。如今SWIG已经支持C/C++的好基友Java,主流脚本语言Python、Perl、Ruby、PHP、JavaScript、tcl、Lua,还有Go、C#,以及R。
  • 使用C共享库从其他语言调用Go函数 此存储库包含文章(medium.com)的源示例。 使用-buildmode=c-shared构建标记,编译器输出标准的共享对象二进制文件(.so),将Go函数公开为C风格的API。 这样,程序员就可以在此...
  • go语言http调用Tushare财经数据包 Tushare平台介绍 Tushare平台网址 https://tushare.pro/document/1,网址上就有下载安装以及数据获取的方式 免费获取,数据准确,适合量化投资分析师(Quant),对金融市场进行大...
  • 不同语言对于传递参数和返回值的实现上会有一些差异,不过无论是在 C、Go 语言这种比较接近系统的编程语言,还是 Ruby、Python 这类语言,它们在函数调用上往往都具有相同的形式,也就是一般包含函数名、参数列表两...
  • Pyhon 调用 Go程序

    2020-08-16 23:11:49
    首先我们使用 Go 语言编写一个简单的整数相加 Add() 函数,并演示在 Python调用的全过程。 编写 Go 代码 package main import "C" //export Add func Add(a, b int) int { return a + b
  • 在c语言中可以用system函数调用系统命令并得到输出,通过输出...我这里介绍下用pythongo语言的实现方式,可以将其它程序的输出直接保存成变量供程序使用。  下面的示例用的是ls命名,需要安装MinGW,并将“C:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 385
精华内容 154
关键字:

python调用go语言

python 订阅