【十】C语言调用Python
文章目录
Python3环境搭建:(备注:在香橙派 3.0.6版本的镜像里已经默认自带了python3.10的版本,不需要安装,只需要后续安装下python3 dev即可。后续统一采用Orangepizero2_3.0.6_ubuntu_jammy_desktop_xfce_linux5.16.17的系统镜像)
一、搭建编译环境
通过C 语言调用 Python 代码,需要先安装 libpython3 的 dev 依赖库(不同的 ubuntu 版本下, python 版本可能会有差异, 比如ubuntu 22.04 里是 libpython3.10-dev )。
1.安装依赖包
首先我们可以使用以下命令来查看是否存在python的dev包.
这个命令用于列出已安装的与 Python 3 开发相关的库。具体来说,它使用 dpkg -l 列出所有已安装的软件包,然后使用 grep 过滤出包含 “libpython3” 和 “dev” 的行。
在 Ubuntu 或 Debian 等基于 Debian 的系统上,你可以运行以下命令:
dpkg -l | grep libpython3
正常会有类似如下的输出,出现"libpython3"和 “dev”,如libpython3.10-dev即可:
如果没有的话我们可以使用以下命令进行安装
sudo apt install libpython3.10-dev
#sudo apt install libpython3.6-dev 安装什么版本具体看自己是什么版本的系统
2.安装完成的图示
**特殊情况:**报错了呜呜呜, 如果安装和下面一样显示失败的话,那我们就需要进行换源了,请看附录部分
二、C语言调用Python
-
头文件
Python.h
,这是Python API的头文件,用于访问Python对象和函数 -
Py_Initialize();
函数初始化Python解释器 -
PyRun_SimpleString();
函数可以执行一段简单的Python代码,例如打印"funny"。需要传递一个字 符串作为参数,表示要执行的Python代码,如print (‘funny’)这么一个Python代码字符串。
函数说明:
int PyRun_SimpleString(const char *command)
这是针对下面 PyRun_SimpleStringFlags() 的简化版接口,将 PyCompilerFlags* 参数设为 NULL;传参就是python执行语句。
Py_Finalize()
函数关闭Python解释器,并释放资源。
#include "Python.h"
int main()
{
Py_Initialize(); // 初始化
PyRun_SimpleString("print ('happy')");
Py_Finalize(); //释放资源
}
运行这个程序我们要使用以下命令进行编译(我的python版本是Python 3.10)
怎么看是什么版本呢?
ls /usr/include/python
然后按一下TAB键
gcc demo1.c -o demo1 -I /usr/include/python3.10 -lpython3.10//编译,根据自己dev链接库的版本号
./demo1 //运行
加餐:关于头文件引用
/usr/include :Linux系统编程往往需要引用c头文件,linux下,头文件一般存储到`/usr/include
若头文件在此文件夹内,相对路径直接引入即可
#include "Python.h"
若在其他文件夹,gcc编译需要声明-I指定路径
gcc -I /usr/xxx/include yyy.c
三、C语言调用Python无参函数
在C语言中调用Python代码通常涉及到使用Python的C API。Python提供了一组C API,允许在C中调用Python函数、处理Python对象等。
1.API简单介绍
一般的调用流程
1.包含Python.h头文件,以便使用Python API。
2.使用void Py_Initialize()初始化Python解释器
3.使用PyObject *PyImport_ImportModule(const char *name)和PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)获取sys.path对象,并利用 int PyList_Append(PyObject *list, PyObject *item)将当前路径.添加到sys.path中,以便加载当前的Python模块(Python文件即python模块)。
第二种写法:
可以改成这两句获取sys.path对象和添加当前路径到sys.path中
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
4.使用PyObject *PyImport_ImportModule(const char *name)函数导入Python模块,并检查是否有错误。
5.使用PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)函数获取 Python函数对象,并检查是否可调用。
6.使用PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)函数调用 Python函数,并获取返回值。
7.使用void Py_DECREF(PyObject *o)函数释放所有引用的Python对象。
8.结束时调用void Py_Finalize()函数关闭Python解释器。
相关的函数参数说明参考网站(网站左上角输入函数名即可开始搜索):
https://docs.python.org/zh-cn/3/c-api/import.html
2.示例代码
下面是一个简单的例子,展示了如何在C中调用Python代码:
1.Python模块: 编写一个简单的Python模块,保存为 nopara.py
。
#nopara.py文件
def say_funny():
print('funny')
2.C代码: 编写一个C程序 invoke.c
,调用Python中的函数。
里面用到了sys.path,具体可以参照附录部分
#include <Python.h>
int main(){
Py_Initialize();//初始化python解释器,他会加载我们python的编译环境
PyObject *pyob = PyImport_ImportModule("sys");//获取sys模块
PyObject *list = PyObject_GetAttrString(pyob,"path");//获取sys.path对象
PyList_Append(list,PyUnicode_FromString("."));//将当前路径添加到sys.path中
//PyUnicode_FromString函数将C语言字符转换为Python字符
/********************************************************************
第二种写法:将567行全部替换掉,更牛逼更简单,同样可以运行
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
*******************************************************************/
// 4.导入nopara模块
PyObject *mode = PyImport_ImportModule("nopara");//导入python模块,就是导入要执行的python文件
if(!mode){
PyErr_Print();
printf("ERROR:mode not!\n");
}
// 5.获取say_funny函数对象
PyObject *pFunc = PyObject_GetAttrString(mode,"say_funny");
if(!pFunc|| !PyCallable_Check(pFunc)){
PyErr_Print();
printf("ERROR:pFunc not!\n");
}
// 6.调用say_funny函数并获取返回值
PyObject *pValue = PyObject_CallObject(pFunc,NULL);
if(!pValue){
PyErr_Print();
printf("ERROR:pValue not!\n");
}
// 7.释放所有引用的Python对象
Py_DECREF(pValue);
Py_DECREF(pFunc);
Py_DECREF(mode);
// 8.关闭Python解释器
Py_Finalize();
return 0;
}
编译语句:
gcc invoke.c -I /usr/include/python3.10 -l python3.10
//“-I”指定python头文件路径;“-l”指定python库文件路径
四、C语言调用Python有参函数
首先定义一个带参数和返回值的函数
def say_funny(category):
print(category)
return category
接下来用C语言进行调用,调用的流程无参函数的调用方式几乎是一样的是的:
#if 0
1、包含Python.h头文件,以便使用Python API。
2、使用void Py_Initialize()初始化Python解释器,
3、使用PyObject *PyImport_ImportModule(const char *name)和PyObject *PyObject_GetAttrString(PyObject *o, const
char *attr_name)获取sys.path对象,并利用int PyList_Append(PyObject *list, PyObject *item)将当前路径.添加到sys.path
中,以便加载当前的Python模块(Python文件即python模块)。
4、使用PyObject *PyImport_ImportModule(const char *name)函数导入Python模块,并检查是否有错误。
5、使用PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)函数获取Python函数对象,并检查是否可调用。6、使用PyObject *Py_BuildValue(const char *format, …)函数将C类型的数据结构转换成Python对象,作为Python函数的参数,没有参数不需要调用
7、使用PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)函数调用Python函数,并获取返回值。
8、使用int PyArg_Parse(PyObject *args, const char *format, …)函数将返回值转换为C类型,并检查是否有错误,没有返回值时不需要调用。
9、使用void Py_DECREF(PyObject *o)函数释放所有引用的Python对象。
10、结束时调用void Py_Finalize()函数关闭Python解释器。#endif
无非多了两步,第六步传参
6、使用PyObject *Py_BuildValue(const char *format, ...)函数创建一个Python元组或者对象,作为Python函数的参数,没有参数势不需要调用
这里要注意的是,Py_BuildValue的第一个参数是类型转换:C对应的Python的数据类型转换对应的格式如下:
关于传参的详细说明请看附录部分
和第八步返回值的处理:
8、使用int PyArg_Parse(PyObject *args, const char *format, ...)函数将返回值转换为C类型,并检查是否有错误,没有返回值时不需要调用。
args: 一个包含传递给函数的参数的元组。
format: 包含格式化指令的字符串,指定了如何解析参数。
...: 用于接收解析后的参数值的变量。
para.py文件
def say_funny(category):
print(category)
return category
invoke2.c文件
#include <Python.h>
int main()
{
Py_Initialize();
// 将当前路径添加到sys.path中
PyObject *sys = PyImport_ImportModule("sys");
PyObject *path = PyObject_GetAttrString(sys, "path");
PyList_Append(path, PyUnicode_FromString("."));
// 导入para模块
PyObject *pModule = PyImport_ImportModule("para");
if (!pModule)
{
PyErr_Print();
printf("Error: failed to load nopara.py\n");
}
//获取say_funny函数对象
PyObject *pFunc = PyObject_GetAttrString(pModule, "say_funny");
if (!pFunc)
{
PyErr_Print();
printf("Error: failed to load say_funny\n");
}
//创建一个字符串作为参数
char *category = "comedy";
PyObject *pArgs = Py_BuildValue("(s)", category);//字符串加括号表示他是一个包含字符串的元组
//调用say_funny函数并获取返回值
PyObject *pValue = PyObject_CallObject(pFunc, pArgs);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
}
//将返回值转换为C类型
char *result = NULL;
if (!PyArg_Parse(pValue, "s", &result))
{
PyErr_Print();
printf("Error: parse failed\n");
}
//打印返回值
printf("pValue=%s\n", result);
//释放所有引用的Python对象
Py_DECREF(pValue);
Py_DECREF(pFunc);
Py_DECREF(pModule);
//释放所有引用的Python对象
Py_Finalize();
return 0;
}
编译:gcc invoke2.c -I /usr/include/python3.10 -l python3.10
运行:./a.out
附录:
1.安装依赖包失败换源
OrangePi ZERO 2 的 apt 软件源由/etc/apt/sources.list
设定,换源的话只需要更改里面的内容就可以了。不过为了保险起见,先对配置文件在同目录进行备份,在命令终端输入下面的命令:
sudo cp /etc/apt/sources.list /etc/apt/sources.list.save
如果我们后期发现因为这方面的原因出现了问题,某些源文件使用不了我们就可以直接输入下面的命令
sudo cp /etc/apt/sources.list.save /etc/apt/sources.list
以下是我们需要更换的开源软件镜像源:
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy main restricted universe multiverse
#deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-security main restricted universe multiverse
#deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/jammy-security main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-updates main restricted universe multiverse
#deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-portsJammy-updates main restricted unlverse multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-backports main restricted universe multiverse
#deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/jammy-backports main restricted unicsbpe@@hv$dr/ghesg
使用以下命令进去把原本的内容都清除掉,打开后,输入ggdG
全部删除(先按两下g
键,再按一下d
键,然后按组合键Shift + g
),粘贴上软件镜像源的内容,然后保存退出即可。
修改完毕之后,使用 apt 进行软件包升级了。
sudo vi /etc/apt/sources.list
更新系统
更新系统可以输入以下命令:
sudo apt update
更新完之后直接安装libpython3的 dev依赖库即可
2.sys.path用法介绍
介绍
sys.path
指定模块搜索路径的列表。默认情况下,python
导入文件或者模块,会在sys.path
里找模块的路径。如果在当前搜索路径列表sys.path
中找不到该模块的话,就会报错。
用法
最常用的用法就是向sys.path
中添加搜索路径
import sys
sys.path.append(path) # path 代表的是一个路径
举例
1.查看当前搜索路径
如果我要导入
python
模块,那么会在这些路径下寻找
示例代码:b.py
import sys
print(sys.path)
运行结果:python3 b.py
2.添加新的路径
我现在有一个模块
new_package
,路径为/home/nsy/nlp/new_package
,我直接import new_package
是会报错的,因为在上面那些路径中找不到import sys import new_package #报错 >>Traceback (most recent call last): File "sys_test.py", line 2, in <module> import new_package ModuleNotFoundError: No module named 'new_package'
添加模块所在路径之后,就可以成功运行啦!!
import sys
sys.path.append('/home/nsy/nlp')
import new_package
print(sys.path)
需要注意的是:添加完之后再看path路径,会增加了自己所需要的路径,即使程序跑完也永远的保存了下来,除非你自己删除掉
3.使用 GCC编译并链接 Python 3.10 的共享库
这段命令和代码的作用是:
ls /usr/include/python
:
- 列出 /usr/include/python 目录下的内容。通常,Python 的头文件(.h 文件)会在这个目录下。
gcc invoke.c -o invoke -I /usr/include/python3.10 -lpython3.10
:
- 使用 GCC 编译 invoke.c 文件,并链接 Python 3.10 的共享库。
- 其中,
-I
选项指定了头文件所在的目录,-lpython3.10 用于链接 Python 3.10 的共享库。 -o
编译后的可执行文件名为 invoke。(如果不指定默认生成a.out可执行文件)./invoke
:运行编译生成的 invoke 可执行文件。
这段命令和代码的目的可能是在 invoke.c 文件中调用了 Python 3.10 的一些函数,并通过 GCC 编译器将其与 Python 3.10 的共享库链接起来。在运行 invoke 可执行文件时,它可能会执行 invoke.c 中的 Python 相关代码。
请确保你的系统中已经安装了 Python 3.10 的开发包,以及 GCC 编译器。如果没有安装,你可以使用系统的包管理工具进行安装。
4.Py_BuildValue函数传参
Py_BuildValue
函数用于将C中的数据转换为Python对象。它的第一个参数是一个格式字符串,指定了如何构建Python对象。以下是一些常见的格式码及其对应的C数据类型:
Python | C类型 |
---|---|
‘s’ | const char * |
‘i’ | int |
‘l’ | long |
‘f’ | double(浮点数) |
‘d’ | double(双精度浮点数) |
‘o’ | PyObject* |
例如:如果你想构建一个Python元组,其中包含一个整数和一个字符串,可以使用以下格式字符串:
PyObject* result = Py_BuildValue("(is)", 42, "hello");
这将构建一个包含一个整数和一个字符串的元组。第一个参数 42
对应 i
表示整数,第二个参数 "hello"
对应 s
表示字符串。
以下是一些常用的格式码:
'()
':元组'[]'
:列表'{}'
:字典's'
:字符串'i'
:整数'l'
:长整数'f'
:浮点数'd'
:双精度浮点数'O'
:任意Python对象
你可以根据实际需要选择合适的格式码,并使用相应的C数据类型提供参数。
确保在使用 Py_BuildValue
时,提供的参数数量和格式码匹配,以避免运行时错误。
格式码 | C数据类型 | 示例 |
---|---|---|
‘s’ | const char * | Py_BuildValue(“s”, “hello”) |
‘i’ | int | Py_BuildValue(“i”, 42) |
‘l’ | long | Py_BuildValue(“l”, 123456789L) |
‘f’ | float | Py_BuildValue(“f”, 3.14f) |
‘d’ | double | Py_BuildValue(“d”, 2.71828) |
‘o’ | PyObject* | Py_BuildValue(“O”, somePyObject) |
‘()’ | Tuple | Py_BuildValue(“(is)”, 42, “hello”) |
‘[]’ | List | Py_BuildValue(“[i]”, 1) |
‘{}’ | Dictionary | Py_BuildValue(“{s:i}”, “key”, 42) |
5.PyArg_Parse函数返回值
int PyArg_Parse(PyObject *args, const char *format, …)函数将返回值转换为C类型, 并检查是否有错误,没有返回值时不需要调用。
PyArg_Parse
是 Python/C API 中用于将 Python 对象参数转换为 C 数据类型的函数。它通常用于解析从 Python 调用的函数传递的参数。
1.以下是 PyArg_Parse
的基本用法:
int PyArg_Parse(PyObject *args, const char *format, ...);
//参数
1.args: 一个包含传递给函数的参数的元组。
2.format: 包含格式化指令的字符串,指定了如何解析参数。
3.... : 用于接收解析后的参数值的变量。
2.PyArg_Parse
的格式化指令可以是以下之一:
'i'
: 整数'f'
: 浮点数's'
: 字符串'O'
: 任意对象
3.以下是一些示例:
3.1解析一个整数参数
int value;
if (!PyArg_Parse(args, "i", &value)) {
// 处理解析错误
return NULL;
}
3.2 解析一个浮点数参数:
double value;
if (!PyArg_Parse(args, "f", &value)) {
// 处理解析错误
return NULL;
}
3.3 解析一个字符串参数:
const char *text;
if (!PyArg_Parse(args, "s", &text)) {
// 处理解析错误
return NULL;
}
3.4 解析一个任意对象参数:
PyObject *obj;
if (!PyArg_Parse(args, "O", &obj)) {
// 处理解析错误
return NULL;
}
4.PyArg_Parse
还支持更复杂的格式化字符串,允许你指定多个参数以及它们的类型。例如:
int x, y;
if (!PyArg_Parse(args, "(ii)", &x, &y)) {
// 处理解析错误
return NULL;
}
在这个例子中,期望参数是一个包含两个整数的元组。
注意:PyArg_Parse
在解析参数时会自动增加引用计数,但在解析失败时需要手动处理错误。如果解析失败,它会返回 0,并且你应该在错误处理中返回 NULL
或适当的错误信息。