opencv-python系列教程来到了最后一讲。感谢一路的坚持。
如何生成OpenCV-Python绑定?
在OpenCV中,所有算法都是用C ++实现的。但是这些算法可以用于不同的语言,如Python,Java等。这可以通过绑定生成器实现。这些生成器在C ++和Python之间架起了一座桥梁,使用户能够从Python调用C ++函数。要全面了解后台发生的情况,需要熟悉Python / C API。有关将C ++函数扩展到Python的简单示例可以在官方Python文档中找到[1]。因此,通过手动编写包装函数将OpenCV中的所有函数扩展到Python是一项非常耗时的任务。所以OpenCV以更智能的方式完成它。OpenCV使用一些位于modules / python / src2中的Python脚本从C ++头文件自动生成这些包装函数。我们下面来看是如何完成这个工作的。。
首先,modules / python / CMakeFiles.txt是一个CMake脚本,它检查要扩展到Python的模块。它会自动检查所有要扩展的模块并获取它们的头文件。这些头文件包含该特定模块的所有类,函数,常量等的列表。
其次,将这些头文件传递给Python脚本modules / python / src2 / gen2.py。这是Python绑定生成器脚本。它调用另一个Python脚本modules / python / src2 / hdr_parser.py。这是头解析器脚本。此标头解析器将完整的头文件拆分为小的Python列表。因此,这些列表包含有关特定函数,类等的所有详细信息。例如,函数名称,返回类型,输入参数,参数类型等的列表。最终列表包含所有函数,结构的详细信息,头文件中的类等。
但是头解析器不解析头文件中的所有函数/类。开发人员必须指定应将哪些函数导出到Python。为此,在这些声明的开头添加了某些宏,这使得标头解析器能够识别要解析的函数。这些宏由编程特定功能的开发人员添加。简而言之,开发人员决定哪些函数应该扩展到Python,哪些不是。这些宏的详细信息将在下一个部分中给出。
因此,头解析器返回解析函数的最终大列表。我们的生成器脚本(gen2.py)将为头解析器解析的所有函数/类/枚举/结构创建包装器函数(您可以在build / modules / python /文件夹中编译这些头文件作为pyopencv_generated _ * .h文件)。但是可能有一些基本的OpenCV数据类型,如Mat,Vec4i,Size。它们需要手动扩展。例如,Mat类型应该扩展为Numpy数组,Size应该扩展为两个整数的元组等。类似地,可能有一些复杂的结构/类/函数等需要手动扩展。所有这些手动包装函数都放在modules / python / src2 / cv2.cpp中。
所以现在唯一剩下的就是编译这些包装文件,它们为我们提供了cv2模块。所以当你在Python中调用一个函数,比如res = equalizeHist(img1,img2)时,你会传递两个numpy数组,你期望另一个numpy数组作为输出。所以这些numpy数组转换为cv :: Mat,然后在C ++中调用equalizeHist()函数。最终结果,res将被转换回Numpy数组。简而言之,几乎所有操作都是用C ++完成的,这使我们的速度几乎与C ++相同。
这是OpenCV-Python绑定生成方式的基本版本。
如何将新模块扩展到Python?
头解析器根据添加到函数声明的一些包装器宏解析头文件。枚举常量不需要任何包装器宏。它们会自动包装。但剩下的函数,类等需要包装器宏。
使用CV_EXPORTS_W宏扩展函数。一个例子如下所示。
CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );
头解析器可以理解来自InputArray,OutputArray等关键字的输入和输出参数。但有时,我们可能需要对输入和输出进行硬编码。为此,使用诸如CV_OUT,CV_IN_OUT等的宏。什么是硬编码(hardcode)呢?参考https://blog.csdn.net/quiet_girl/article/details/50579997
1. 官方解释:
在计算机程序或文本编辑中; hardcode(这个词比hard code用起来要频繁一些)是指将可变变量用一个固定值来代替的方法。用这种方法编译后; 如果以后需要更改此变量就非常困难了。大部分程序语言里; 可以将一个固定数值定义为一个标记 。
2.
hard code是指“硬编码”,即将数据直接写在代码(程序)中。也就是,在程序中,直接给变量赋值。指的是在软件实现上,把输出或输入的相关参数(例如:路径、输出的形式、格式)直接硬编码在源代码中,而非在运行时期由外界指定的设置、资源、数据、或者格式做出适当回应。
3.
hard code的双重性:(1)直接将数据填写在源代码中,数据发生变化时,并不利于数据的修改,会造成程序的质量降低.(2)保护一些数据,直接赋值,避免其发生变化。
CV_EXPORTS_W void minEnclosingCircle( InputArray points,
CV_OUT Point2f& center, CV_OUT float& radius );
对于大类,也使用CV_EXPORTS_W。要扩展类方法,请使用CV_WRAP。类似地,CV_PROP用于类字段。
classCV_EXPORTS_W CLAHE : public Algorithm
{
public:
CV_WRAP virtual void apply(InputArray src, OutputArray dst) = 0;
CV_WRAP virtual void setClipLimit(double clipLimit) = 0;
CV_WRAP virtual double getClipLimit() const = 0;
}
可以使用CV_EXPORTS_AS扩展重载的函数。但我们需要传递一个新名称,以便在Python中使用该名称调用每个函数。以下面的积分函数为例。有三个函数可用,因此每个函数都以Python后缀命名。类似地,CV_WRAP_AS可用于包装重载类方法。
CV_EXPORTS_W void integral( InputArray src, OutputArray sum, int sdepth = -1 );
CV_EXPORTS_AS(integral2) void integral( InputArray src, OutputArray sum,
OutputArray sqsum, int sdepth = -1, int sqdepth = -1 );
CV_EXPORTS_AS(integral3) void integral( InputArray src, OutputArray sum,
OutputArray sqsum, OutputArray tilted,
int sdepth = -1, int sqdepth = -1 );
使用CV_EXPORTS_W_SIMPLE扩展小类/结构。这些结构通过值传递给C ++函数。例如KeyPoint,Match等。他们的方法由CV_WRAP扩展,字段由CV_PROP_RW扩展。
classCV_EXPORTS_W_SIMPLE DMatch
{
public:
CV_WRAP DMatch();
CV_WRAP DMatch(int _queryIdx, int _trainIdx, float _distance);
CV_WRAP DMatch(int _queryIdx, int _trainIdx, int _imgIdx, float _distance);
CV_PROP_RW int queryIdx; // query descriptor index
CV_PROP_RW int trainIdx; // train descriptor index
CV_PROP_RW int imgIdx; // train image index
CV_PROP_RW float distance;
};
可以使用CV_EXPORTS_W_MAP导出其他一些小类/结构,并将其导出到Python本机字典。Moments()就是一个例子。
classCV_EXPORTS_W_MAP Moments
{
public:
CV_PROP_RW double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03;
CV_PROP_RW double mu20, mu11, mu02, mu30, mu21, mu12, mu03;
CV_PROP_RW double nu20, nu11, nu02, nu30, nu21, nu12, nu03;
};
所以这些是OpenCV中可用的主要扩展宏。通常,开发人员必须将适当的宏放在适当的位置。其余由生成器脚本完成。有时,可能存在生成器脚本无法创建包装器的特殊情况。这些函数需要手动处理,为此,编写自己的pyopencv _ * .hpp扩展头文件并将它们放入模块的misc / python子目录中。但大多数情况下,根据OpenCV编码指南编写的代码将由生成器脚本自动包装。
但是这个过程其实是很麻烦的,因为如果要自己编写opencv的c++代码,就需要下载opencv的源码,然后在把自己的代码加入,然后再编译,这个是很费时间的。
opencv源码里面是有这些东西的,我就不演示这个东西了,一般都不会自己去开发一些c++代码(如果真的对于速度要求很高),可以在有需求的时候会来看一看。
python和c++互调
参考https://www.cnblogs.com/yanzi-meng/p/8066944.html
和https://www.cnblogs.com/yanzi-meng/p/8066947.html
Python调用C/C++
1、Python调用C动态链接库
Python调用C库比较简单,不经过任何封装打包成so,再使用python的ctypes调用即可。
2)gcc编译生成动态库libpycall.so:gcc -o libpycall.so -shared -fPIC pycall.c。使用g++编译生成C动态库的代码中的函数或者方法时,需要使用extern "C"来进行编译。
--share生成的是动态库,动态库和静态库参考https://blog.csdn.net/foooooods/article/details/80259395
这个-fPIC参考http://c.biancheng.net/view/2385.html
(3)Python调用动态库的文件:pycall.py
我们来试一试:
在powershell里面是这样的。但是在sublime里面:
感觉挺奇怪的。
2、Python调用C++(类)动态链接库
需要extern "C"来辅助,也就是说还是只能调用C函数,不能直接调用方法,但是能解析C++方法。不是用extern "C",构建后的动态链接库没有这些函数的符号表。
(1)C++类文件:pycallclass.cpp
#include
using namespace std;
class TestLib
{
public:
void display();
void display(int a);
};
void TestLib::display() {
cout<
}
void TestLib::display(int a) {
cout<
}
extern "C" {
TestLib obj;
void display() {
obj.display();
}
void display_int() {
obj.display(2);
}
}
(3)Python调用动态库的文件:pycallclass.py
import ctypes
so = ctypes.cdll.LoadLibrary
lib = so("./libpycallclass.so")
print 'display()'
lib.display()
print 'display(100)'
lib.display_int(100)
这个是cpp程序,所以要用g++编译:
不过出现了一些问题。这个extern "C"是干嘛的呢?参考https://blog.csdn.net/jiqiren007/article/details/5933599
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。
这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。
这个功能主要用在下面的情况:
1、C++代码调用C语言代码
2、在C++的头文件中使用
3、在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到
给出一个我设计的例子:
moduleA、moduleB两个模块,B调用A中的代码,其中A是用C语言实现的,而B是利用C++实现的,下面给出一种实现方法:
//moduleA头文件
#ifndef __MODULE_A_H //对于模块A来说,这个宏是为了防止头文件的重复引用
#define __MODULE_A_H
int fun(int, int);
#endif
//moduleA实现文件moduleA.C //模块A的实现部分并没有改变
#include"moduleA"
int fun(int a, int b)
{
return a+b;
}
//moduleB头文件
#idndef __MODULE_B_H //很明显这一部分也是为了防止重复引用
#define __MODULE_B_H
#ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件, extern "C"{ //因为cpp文件默认定义了该宏),则采用C语言方式进行编译
#include"moduleA.h"
#endif
… //其他代码
#ifdef __cplusplus
}
#endif
#endif
//moduleB实现文件 moduleB.cpp //B模块的实现也没有改变,只是头文件的设计变化了
#include"moduleB.h"
int main()
{
cout<
}
下面是详细的介绍:
由于C、C++编译器对函数的编译处理是不完全相同的,尤其对于C++来说,支持函数的重载,编译后的函数一般是以函数名和形参类型来命名的。
例如函数void fun(int, int),编译后的可能是(不同编译器结果不同)_fun_int_int(不同编译器可能不同,但都采用了类似的机制,用函数名和参数类型来命名编译后的函数名);而C语言没有类似的重载机制,一般是利用函数名来指明编译后的函数名的,对应上面的函数可能会是_fun这样的名字。
看下面的一个面试题:为什么标准头文件都有类似的结构?
#ifndef __INCvxWorksh /*防止该头文件被重复引用*/
#define __INCvxWorksh
#ifdef __cplusplus //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
extern "C"{
#endif
/*…*/
#ifdef __cplusplus
}
#endif
#endif /*end of __INCvxWorksh*/
分析:显然,头文件中编译宏"#ifndef __INCvxWorksh 、#define __INCvxWorksh、#endif"(即上面代码中的蓝色部分)的作用是为了防止该头文件被重复引用那么
#ifdef __cplusplus (其中__cplusplus是cpp中自定义的一个宏!!!)
extern "C"{
#endif
#ifdef __cplusplus
}
#endif
的作用是什么呢?
extern "C"包含双重含义,从字面上可以知道,首先,被它修饰的目标是"extern"的;其次,被它修饰的目标代码是"C"的。被extern "C"限定的函数或变量是extern类型的
extern是C/C++语言中表明函数和全局变量的作用范围的关键字,该关键字告诉编译器,其申明的函数和变量可以在本模块或其他模块中使用。
记住,下面的语句:
extern int a; 仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出错。
通常来说,在模块的头文件中对本模块提供给其他模块引用的函数和全局变量以关键字extern生命。例如,如果模块B要引用模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但并不会报错;它会在链接阶段从模块A编译生成的目标代码中找到该函数。
extern对应的关键字是static,static表明变量或者函数只能在本模块中使用,因此,被static修饰的变量或者函数不可能被extern C修饰。被extern "C"修饰的变量和函数是按照C语言方式进行编译和链接的:这点很重要!!!!
上面也提到过,由于C++支持函数重载,而C语言不支持,因此函数被C++编译后在符号库中的名字是与C语言不同的;C++编译后的函数需要加上参数的类型才能唯一标定重载后的函数,而加上extern "C"后,是为了向编译器指明这段代码按照C语言的方式进行编译
未加extern "C"声明时的链接方式:
//模块A头文件 moduleA.h
#idndef _MODULE_A_H
#define _MODULE_A_H
int foo(int x, int y);
#endif
在模块B中调用该函数:
//模块B实现文件 moduleB.cpp
#include"moduleA.h"
foo(2,3);
实际上,在链接阶段,连接器会从模块A生成的目标文件moduleA.obj中找_foo_int_int这样的符号!!!,显然这是不可能找到的,因为foo()函数被编译成了_foo的符号,因此会出现链接错误。
常见的做法可以参考下面的一个实现:
moduleA、moduleB两个模块,B调用A中的代码,其中A是用C语言实现的,而B是利用C++实现的,下面给出一种实现方法:
//moduleA头文件
#ifndef __MODULE_A_H //对于模块A来说,这个宏是为了防止头文件的重复引用
#define __MODULE_A_H
int fun(int, int);
#endif
//moduleA实现文件moduleA.C //模块A的实现部分并没有改变
#include"moduleA"
int fun(int a, int b)
{
return a+b;
}
//moduleB头文件
#idndef __MODULE_B_H //很明显这一部分也是为了防止重复引用
#define __MODULE_B_H
#ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件, extern "C"{ //因为cpp文件默认定义了该宏),则采用C语言方式进行编译
#include"moduleA.h"
#endif
… //其他代码
#ifdef __cplusplus
}
#endif
#endif
//moduleB实现文件 moduleB.cpp //B模块的实现也没有改变,只是头文件的设计变化了
#include"moduleB.h"
int main()
{
cout<
}
extern "C"的使用要点
1. 可以是单一语句
extern "C" double sqrt(double);
2. 可以是复合语句, 相当于复合语句中的声明都加了extern "C"
extern "C"
{
double sqrt(double);
int min(int, int);
}
3.可以包含头文件,相当于头文件中的声明都加了extern "C"
extern "C"
{
#i nclude
}
4. 不可以将extern "C" 添加在函数内部
5. 如果函数有多个声明,可以都加extern "C", 也可以只出现在第一次声明中,后面的声明会接受第一个链接指示符的规则。
6. 除extern "C", 还有extern "FORTRAN" 等。
貌似现在可以不加这个extern "C"的。不过python调用还是不行:
这个不加上extern "C"还是不行的。加上去就可以了:
这个打印顺序就很对。虽然不知道为什么第一次就有问题。需要加extern "C"是因为python只能调用C语言的代码。
3、Python调用C/C++可执行程序
主要用到的是subprocess模块。参考https://www.jb51.net/article/142787.htm
subprocess最早在2.4版本引入。用来生成子进程,并可以通过管道连接他们的输入/输出/错误,以及获得他们的返回值。
subprocess用来替换多个旧模块和函数:os.system
os.spawn*
os.popen*
popen2.*
commands.*
运行python的时候,我们都是在创建并运行一个进程,linux中一个进程可以fork一个子进程,并让这个子进程exec另外一个程序。在python中,我们通过标准库中的subprocess包来fork一个子进程,并且运行一个外部的程序。subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所欲我们可以根据需要来从中选取一个使用。另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。
这个用g++生成一个testmain.exe。
这个返回是1,说明有问题。
在cmd里面执行exe不能加./,虽然powershell可以。
这样就没有问题了。
我们修改一下,把最后一句的输出以stderr输出。
重新g++。
有一些输出结构就没有了end,还有的把end提前了。
而且提前的程度还不一样。
和预期的不太一样:
stdout倒是把cerr打印出来了,stderr什么都没有,很奇怪。linux和c++的这些标准似乎不太一样。stderr应该是程序出错的信息,但是我们运行这个程序没有出错,cerr是c++输出的一个等级而已。这么看来stderr是没有问题的。不过stdout有点奇怪。
8920就是pid。
4、扩展Python(C++为Python编写扩展模块)
所有能被整合或导入到其它python脚本的代码,都可以被称为扩展。可以用Python来写扩展,也可以用C和C++之类的编译型的语言来写扩展。Python在设计之初就考虑到要让模块的导入机制足够抽象。抽象到让使用模块的代码无法了解到模块的具体实现细节。Python的可扩展性具有的优点:方便为语言增加新功能、具有可定制性、代码可以实现复用等。
为 Python 创建扩展需要三个主要的步骤:创建应用程序代码、利用样板来包装代码和编译与测试。
#include
#include
#include
int fac(int n)
{
if (n < 2) return(1); /* 0! == 1! == 1 */
return (n)*fac(n-1); /* n! == n*(n-1)! */
}
char *reverse(char *s)
{
register char t, /* tmp */
*p = s, /* fwd */
*q = (s + (strlen(s) - 1)); /* bwd */
while (p < q) /* if p < q */
{
t = *p; /* swap & move ptrs */
*p++ = *q;
*q-- = t;
}
return(s);
}
int main()
{
char s[BUFSIZ];
printf("4! == %d\n", fac(4));
printf("8! == %d\n", fac(8));
printf("12! == %d\n", fac(12));
strcpy(s, "abcdef");
printf("reversing 'abcdef', we get '%s'\n", \
reverse(s));
strcpy(s, "madam");
printf("reversing 'madam', we get '%s'\n", \
reverse(s));
return 0;
}
述代码中有两个函数,一个是递归求阶乘的函数fac();另一个reverse()函数实现了一个简单的字符串反转算法,其主要目的是修改传入的字符串,使其内容完全反转,但不需要申请内存后反着复制的方法。
(2)用样板来包装代码
接口的代码被称为“样板”代码,它是应用程序代码与Python解释器之间进行交互所必不可少的一部分。样板主要分为4步:a、包含Python的头文件;b、为每个模块的每一个函数增加一个型如PyObject* Module_func()的包装函数;c、为每个模块增加一个型如PyMethodDef ModuleMethods[]的数组;d、增加模块初始化函数void initModule()。
#include
#include
#include
int fac(int n)
{
if (n < 2) return(1);
return (n)*fac(n-1);
}
char *reverse(char *s)
{
register char t,
*p = s,
*q = (s + (strlen(s) - 1));
while (s && (p < q))
{
t = *p;
*p++ = *q;
*q-- = t;
}
return(s);
}
int test()
{
char s[BUFSIZ];
printf("4! == %d\n", fac(4));
printf("8! == %d\n", fac(8));
printf("12! == %d\n", fac(12));
strcpy(s, "abcdef");
printf("reversing 'abcdef', we get '%s'\n", \
reverse(s));
strcpy(s, "madam");
printf("reversing 'madam', we get '%s'\n", \
reverse(s));
return 0;
}
#include "Python.h"
static PyObject *
Extest_fac(PyObject *self, PyObject *args)
{
int num;
if (!PyArg_ParseTuple(args, "i", &num))
return NULL;
return (PyObject*)Py_BuildValue("i", fac(num));
}
static PyObject *
Extest_doppel(PyObject *self, PyObject *args)
{
char *orig_str;
char *dupe_str;
PyObject* retval;
if (!PyArg_ParseTuple(args, "s", &orig_str))
return NULL;
retval = (PyObject*)Py_BuildValue("ss", orig_str,
dupe_str=reverse(strdup(orig_str)));
free(dupe_str); #防止内存泄漏
return retval;
}
static PyObject *
Extest_test(PyObject *self, PyObject *args)
{
test();
return (PyObject*)Py_BuildValue("");
}
static PyMethodDef
ExtestMethods[] =
{
{ "fac", Extest_fac, METH_VARARGS },
{ "doppel", Extest_doppel, METH_VARARGS },
{ "test", Extest_test, METH_VARARGS },
{ NULL, NULL },
};
void initExtest()
{
Py_InitModule("Extest", ExtestMethods);
}
增加包装函数,所在模块名为Extest,那么创建一个包装函数叫Extest_fac(),在Python脚本中使用是先import Extest,然后调用Extest.fac(),当Extest.fac()被调用时,包装函数Extest_fac()会被调用,包装函数接受一个 Python的整数参数,把它转为C的整数,然后调用C的fac()函数,得到一个整型的返回值,最后把这个返回值转为Python的整型数做为整个函数调用的结果返回回去。其他两个包装函数Extest_doppel()和Extest_test()类似。
从Python到C的转换用PyArg_Parse*系列函数,int PyArg_ParseTuple():把Python传过来的参数转为C;int PyArg_ParseTupleAndKeywords()与PyArg_ParseTuple()作用相同,但是同时解析关键字参数;它们的用法跟C的sscanf函数很像,都接受一个字符串流,并根据一个指定的格式字符串进行解析,把结果放入到相应的指针所指的变量中去,它们的返回值为1表示解析成功,返回值为0表示失败。从C到Python的转换函数是PyObject* Py_BuildValue():把C的数据转为Python的一个对象或一组对象,然后返回之;Py_BuildValue的用法跟sprintf很像,把所有的参数按格式字符串所指定的格式转换成一个Python的对象。
C与Python之间数据转换的转换代码:
为每个模块增加一个型如PyMethodDef ModuleMethods[]的数组,以便于Python解释器能够导入并调用它们,每一个数组都包含了函数在Python中的名字,相应的包装函数的名字以及一个METH_VARARGS常量,METH_VARARGS表示参数以tuple形式传入。 若需要使用PyArg_ParseTupleAndKeywords()函数来分析命名参数的话,还需要让这个标志常量与METH_KEYWORDS常量进行逻辑与运算常量 。数组最后用两个NULL来表示函数信息列表的结束。
所有工作的最后一部分就是模块的初始化函数,调用Py_InitModule()函数,并把模块名和ModuleMethods[]数组的名字传递进去,以便于解释器能正确的调用模块中的函数。
不过
不过这个文件已经放在github上了。
(3)编译
为了让新Python的扩展能被创建,需要把它们与Python库放在一起编译,distutils包被用来编译、安装和分发这些模块、扩展和包。
创建一个setup.py 文件,编译最主要的工作由setup()函数来完成:
借鉴了很多代码https://blog.csdn.net/gitchat/article/details/78537098
https://blog.csdn.net/maosijunzi/article/details/79354806
https://blog.csdn.net/a8572785/article/details/10427521
https://blog.csdn.net/marscrazy_90/article/details/14045683
发现很多人用的是vs这种东西,我表示拒绝。
还是https://blog.csdn.net/zong596568821xp/article/details/81133511里面的好用:
这样做还是不行
还是疯狂出现问题:
没办法了。
上面的error: Unable to find vcvarsall.bat可以参考https://www.cnblogs.com/yyds/p/7065637.html
说是需要安装vs.........。不过https://blog.csdn.net/a2099948768/article/details/81738853里不需要。不过还是不行。还有https://bytes.com/topic/python/answers/803071-trying-extend-python-c-undefined-reference-py_buildvalue
2 通过boost实现
我们使用和上面同样的例子,实现test2.cpp如下
好吧,看到boost库似乎安装也需要用到vs。https://blog.csdn.net/lp310018931/article/details/47791143
好吧,看来windows不下载vs搞这玩意还挺难搞,我真的不想下vs。以后有时间再在linux上试试,我现在不太向开虚拟机。
还有一种方法,不过还是用的vs。https://blog.csdn.net/a8572785/article/details/10427521
似乎有不用vs就可以的https://stackoverflow.com/questions/5299468/unable-to-build-boost-libraries-with-gcc
从boost官网下载代码。
然后点b2.exe。
就开始编译了,看到确实是在用gcc:
这个过程需要一会。然后就生成了一些库。但是我按照步骤来还是有一堆未定义的引用。
运行bjam.exe试了试。参考https://stackoverflow.com/questions/10403720/building-boost-python-with-mingw-on-windows7-64bit
好像说boost对于python3的支持不是很好。
enmmm。郁闷。试试swig吧。
参考https://blog.csdn.net/a8572785/article/details/10427521
看到
据https://bbs.csdn.net/topics/392076263说是还得链接到libpython2.7.a,但是我用的是python3啊。
好吧,这块只能先放弃了。
不过又发现了新大陆:
https://www.cnblogs.com/lyrichu/p/6822861.html
python作为一门脚本语言,其好处是语法简单,很多东西都已经封装好了,直接拿过来用就行,所以实现同样一个功能,用Python写要比用C/C++代码量会少得多。但是优点也必然也伴随着缺点(这是肯定的,不然还要其他语言干嘛),python最被人诟病的一个地方可能就是其运行速度了。这这是大部分脚本语言共同面对的问题,因为没有编译过程,直接逐行执行,所以要慢了一大截。所以在一些对速度要求很高的场合,一般都是使用C/C++这种编译型语言来写。但是很多时候,我们既想使用python的简介优美,又不想损失太多的性能,这个时候有没有办法将python与C/C++结合到一起呢?这样在性能与速度要求不高的地方,可以用pyhton写,而关键的运算部分用C/C++写,这样就太好了。python在做科学计算或者数据分析时,这是一个非常普遍的需求。要想实现这个功能,python为我们提供了不止一种解决办法。下面我就逐一给大家介绍。
一、Cython 混合python与C
官方网址:http://docs.cython.org/en/latest/src/quickstart/overview.html。首先来看看cython的官方介绍吧。
[Cython] is a programming language that makes writing C extensions for the Python language as easy as Python itself. It aims to become a superset of the [Python]language which gives it high-level, object-oriented, functional, and dynamic programming. Its main feature on top of these is support for optional static type declarations as part of the language. The source code gets translated into optimized C/C++ code and compiled as Python extension modules. This allows for both very fast program execution and tight integration with external C libraries, while keeping up the high programmer productivity for which the Python language is well known.
简单来说,cython就是一个内置了c数据类型的python,它是一个python的超集,兼容几乎所有的纯python代码,但是又可以使用c的数据类型。这样就可以同时使用c库,又不失python的优雅。
好了,不讲太多废话,直接来看cython如何使用吧。这里的介绍大部分来自官网,由于cython涉及到的东西还比较多,所以这里只是简单的入门介绍,详细的信息请移步英文官网。
使用cython有两种方式:第一个是编译生成Python扩展文件(有点类似于dll,即动态链接库),可以直接import使用。第二个是使用jupyter notebook或sage notebook 内联 cython代码。
先看第一种。还是举最经典的hello world的例子吧。新建一个hello.pyx文件,定义一个hello函数如下:
def hello(name): print("Hello %s." % name)
然后,我们来写一个setup.py 文件(写python扩展几乎都要写setup.py文件,我之前也简单介绍过怎么写)如下
这个用的是linux呢。
其中 ext_modules 里面写你要 编译的.pyx文件名字。OK,所有工作都完成了。接下来,进入cmd,切换到setup.py 所在的文件,然后执行命令: python setup.py build_ext --inplace 就会编译生成一个build 文件夹以及一个.pyd文件了,这个pyd文件就是python的动态扩展库,--inplace 的意思是在当前文件目录下生成.pyd文件,不加这一句就会在build文件夹中生成。截图如下:
可以看出,除了生成了一个pyd文件之外,还生成了一个.c文件。test.py是我们用来测试的文件,在里面写如下内容:
from hello import hello
hello("lyric")
从hello 模块导入 hello函数,然后直接调用就可以了。结果输出 Hello lyric.
这个成功了,首先我的电脑上是用anaconda安装的python,cpython是自带的。而且我电脑上其实还有装过一个4G的visual studio c++ build tools,这个东西并不是vs。链接: https://pan.baidu.com/s/1d-DULJ5IqbcloTrtugy1xQ 提取码: zbzj 复制这段内容后打开百度网盘手机App,操作更方便哦
总之是成功了。
不过这个例子感觉和C语言毫无关系,虽然有一个hello.c。
这个东西居然有2559行。cpython官网:https://cython.org/
下一讲就研究一下如何使cpython.