python扩展实现方法--python与c混和编程

news2025/1/16 11:12:04

大部分的Python的扩展都是用C语言写的,但也很容易移植到C++中。

一般来说,所有能被整合或者导入到其它python脚本的代码,都可以称为扩展。

扩展可以用纯Python来写,也可以用C或者C++之类的编译型的语言来扩展。

就算是相同的架构的两台电脑之间最好也不要互相共享二进制文件,最好在各自的

电脑上编译Python和扩展。因为就算是编译器或者CPU之间的些许差异。

需要扩展Python语言的理由:

1. 添加/额外的(非Python)功能,提供Python核心功能中没有提供的部分,比如创建新的

数据类型或者将Python嵌入到其它已经存在的应用程序中,则必须编译。

2. 性能瓶颈的效率提升, 解释型语言一般比编译型语言慢,想要提高性能,全部改写成编译型

语言并不划算,好的做法是,先做性能测试,找出性能瓶颈部分,然后把瓶颈部分在扩展中实现,

是一个比较简单有效的做法。

3. 保持专有源代码的私密,脚本语言一个共同的缺陷是,都是执行的源代码,保密性便没有了。

把一部分的代码从Python转到编译语言就可以保持专有源代码私密性。不容易被反向工程,对涉及

到特殊算法,加密方法,以及软件安全时,这样做就显得很重要。

另一种对代码保密的方式是只发布预编译后的.pyc文件,是一种折中的方法。

创建Python扩展的步骤

1. 创建应用程序代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFSIZE 10

int fac(int n) {
    if (n < 2)
        return 1;
    return n * fac(n - 1);
}

char *reverse(char *s) {
    register char t;
    char *p = s;
    char *q = (s + (strlen(s) - 1));
    while (p < q) {
        t = *p;
        *p++ = *q;
        *q-- = t;
    }
    return s;
}

int main() {
    char s[BUFSIZE];
    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;
}

一般是需要写main()函数,用于单元测试

使用gcc进行编译

>gcc Extest.c -o Extest

执行

>./Extest

2. 利用样板来包装代码

整个扩展的实现都是围绕"包装"这个概念来进行的。你的设计要尽可能让你的实现语言与Python无缝结合。

接口的代码又被称为"样板"代码,它是你的代码与Python解释器之间进行交互所必不可少的部分:

我们的样板代码分为4步:

a. 包含python的头文件

需要找到python的头文件在哪,一般是在/usr/local/include/python2.x中

在上面的C代码中加入#include "Python.h"

b. 为每个模块的每一个函数增加一个型如PyObject* Module_func()的包装函数

包装函数的用处就是先把python的值传递给c,再把c中函数的计算结果转换成Python对象返回给python。

需要为所有想被Python环境访问到的函数都增加一个静态函数,返回类型为PyObject *,函数名格式为

模块名_函数名;

static PyObject * Extest_fac(PyObject *self, PyObject *args) {
    int res;//计算结果值
    int num;//参数
    PyObject* retval;//返回值

    //i表示需要传递进来的参数类型为整型,如果是,就赋值给num,如果不是,返回NULL;
    res = PyArg_ParseTuple(args, "i", &num);
    if (!res) {
        //包装函数返回NULL,就会在Python调用中产生一个TypeError的异常
        return NULL;
    }
    res = fac(num);
    //需要把c中计算的结果转成python对象,i代表整数对象类型。
    retval = (PyObject *)Py_BuildValue("i", res);
    return retval;
}

也可以写成更简短,可读性更强的形式:

static PyObject * Extest_fac(PyObject *self, PyObject *args) {
    int m;
    if (!(PyArg_ParseTuple(args, "i", &num))) {
        return NULL;
    }
    return (PyObject *)Py_BuildValue("i", fac(num));
}

下面是python和c对应的类型转换参数表:

这里还有一个Py_BuildValue的用法表:

reverse函数的包装也类似:

static PyObject *
Extest_reverse(PyObject *self, PyObject *args) {
    char *orignal;
    if (!(PyArg_ParseTuple(args, "s", &orignal))) {
        return NULL;
    }
    return (PyObject *)Py_BuildValue("s", reverse(orignal));
}

也可以再改造成返回包含原始字串和反转字串的tuple的函数

static PyObject *
Extest_doppel(PyObject *self, PyObject *args) {
    char *orignal;
    if (!(PyArg_ParseTuple(args, "s", &orignal))) {
        return NULL;
    }
    //ss,就可以返回两个字符串,应该reverse是在原字符串上进行操作,所以需要先strdup复制一下
    return (PyObject *)Py_BuildValue("ss", orignal, reverse(strdup(orignal)));
}

上面的代码有什么问题呢?

和c语言相关的问题,比较常见的就是内存泄露。。。上面的例子中,Py_BuildValue()函数生成

要返回Python对象的时候,会把转入的数据复制一份。上面的两个字符串都被复制出来。但是

我们申请了用于存放第二个字符串的内存,在退出的时候没有释放掉它。于是内存就泄露了。

正确的做法是:先生成返回的python对象,然后释放在包装函数中申请的内存。

static PyObject *
Extest_doppel(PyObject *self, PyObject *args) {
    char *orignal;
    char *reversed;
    PyObject * retval;
    if (!(PyArg_ParseTuple(args, "s", &orignal))) {
        return NULL;
    }
    retval = (PyObject *)Py_BuildValue("ss", orignal, reversed=reverse(strdup(orignal)));
    free(reversed);
    return retval;
}

c. 为每个模块增加一个型如PyMethodDef ModuleMethods[]的数组

我们已经创建了几个包装函数,需要在某个地方把它们列出来,以便python解释器能够导入并调用它们。

这个就是ModuleMethods[]数组所需要做的事情。

格式如下 ,每一个数组都包含一个函数的信息,最后一个数组放置两个NULL值,代表声明结束

static PyMethodDef
ExtestMethods[] = {
    {"fac", Extest_fac, METH_VARARGS},
    {"doppel", Extest_doppel, METH_VARARGS},
    {"reverse", Extest_reverse, METH_VARARGS},
    {NULL, NULL},
};

METH_VARARGS代表参数以tuple的形式传入。如果我们需要使用PyArg_ParseTupleAndKeywords()

函数来分析关键字参数的话,这个标志常量应该写成: METH_VARARGS & METH_KEYWORDS,进行逻辑与运算。

d. 增加模块初始化函数void initMethod()

最后的工作就是模块的初始化工作。这部分代码在模块被python导入时进行调用。

void initExtest() {
    Py_InitModule("Extest", ExtestMethods);
}

最终代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Python.h"

#define BUFSIZE 10

int fac(int n) {
    if (n < 2)
        return 1;
    return n * fac(n - 1);
}

char *reverse(char *s) {
    register char t;
    char *p = s;
    char *q = (s + (strlen(s) - 1));
    while (p < q) {
        t = *p;
       *p++ = *q;
       *q-- = t;
    }
    return s;
}

static PyObject *
Extest_fac(PyObject *self, PyObject *args) {
    int res;
    int num;
    PyObject* retval;

    res = PyArg_ParseTuple(args, "i", &num);
    if (!res) {
        return NULL;
    }
    res = fac(num);
    retval = (PyObject *)Py_BuildValue("i", res);
    return retval;
}

static PyObject *
Extest_reverse(PyObject *self, PyObject *args) {
    char *orignal;
    if (!(PyArg_ParseTuple(args, "s", &orignal))) {
        return NULL;
    }
    return (PyObject *)Py_BuildValue("s", reverse(orignal));
}

static PyObject *
Extest_doppel(PyObject *self, PyObject *args) {
    char *orignal;
    char *resv;
    PyObject *retval;
    if (!(PyArg_ParseTuple(args, "s", &orignal))) {
        return NULL;
    }
    retval = (PyObject *)Py_BuildValue("ss", orignal, resv=reverse(strdup(orignal)));
    free(resv);
    return retval;
}

static PyMethodDef
ExtestMethods[] = {
    {"fac", Extest_fac, METH_VARARGS},
    {"doppel", Extest_doppel, METH_VARARGS},
    {"reverse", Extest_reverse, METH_VARARGS},
    {NULL, NULL},
};

void initExtest() {
    Py_InitModule("Extest", ExtestMethods);
}

int main() {
    char s[BUFSIZE];
    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));
    test();
    return 0;
}

3. 编译与测试

为了让你的新python扩展能够被创建,你需要把它们与python库放在一起编译。python中的distutils包被

用来编译,安装和分发这些模块,扩展和包。步骤如下:

a. 创建setup.py

我们在安装python第三方包的时候,很多情况下会用到python setup.py install这个命令,

下面我们来了解一下setup.py文件的内容。

编译的最主要的内容由setup函数完成,你需要为每一个扩展创建一个Extension实例,在这里我们只有一个

扩展,所以只需要创建一个实例。

Extension('Extest', sources=['Extest.c']),第一个参数是扩展的名字,如果模块是包的一部分,还需要加".";

第二个参数是源代码文件列表

setup('Extest', ext_modules=[...]),第一个参数表示要编译哪个东西,第二个参数列出要编译的Extension对象。

#!/usr/bin/env python
from distutils.core import setup, Extension
    MOD = 'Extest'
    setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest.c'])])

setup函数还有很多选项可以设置。详情可见官网。

2. Writing the Setup Script — Python 3.11.1 documentation

b. 通过运行setup.py来编译和连接你的代码

在shell中运行命令

>python setup.py build

当你报错如:无法找到Python.h文件

那么说明你没有安装python-dev包,需要去官网下载源码包重装自己编译安装一下python。

Python.h文件一般会出现在/usr/include/Python2.X文件夹中,我这里反正是没有的。。。

只有重新编译一个python...

我现在linux系统上的python版本是2.6.6,我下载一个相同版本的源码,也可以下载更高版本。

Python Release Python 2.6.6 | Python.org

解压源码包

> tar xzf Python-2.6.6.tgz

> cd Python-2.6.6.tgz

编译安装Python

> ./configure --prefix=/usr/local/python2.6

> make

> sudo make install

创建一个新编译python的链接

> sudo ln -sf /usr/local/python2.6/bin/python2.6 /usr/bin/python2.6

测试一下,可用

使用这种方法可以在Linux上运行不同版本的python.

Python.h文件也在/usr/local/python2.6/include/python2.6路径下找到。

重新运行编译

编译成功后,你的扩展就会被创建在bulid/lib.*目录下。你会看到一个.so文件,这是linux下的

动态库文件:

c. 进行调试

你可以直接用python代码调用进行测试:

#!/usr/bin/python
from ctypes import *
import os
#需要使用绝对路径
extest = cdll.LoadLibrary(os.getcwd() + '/Extest.so')
print extest.fac(4)

也可以在当前目录下执行命令,安装到你的python路径下

> python setup.py install

安装成功的话,直接导入测试:

最后需要注意一点的是,原来的c文件中有一个main函数,因为一个系统中只能有一个main

函数,所以为了不起冲突,可以把main函数改成test函数,再用Extest_test()包装函数处理一下,

再加入ExtestMethods数组,这样就可以调用这个测试函数了。

static PyObject *
Extest_test(PyObject *self, PyObject *args) {
    test();
    #返回空的话,就使用下面这一句 
    return (PyObject *)Py_BuildValue("");
}

简单性能比较

测试代码

import Extest
import time

start = time.time()
a = Extest.reverse("abcd")
timeC = time.time() - start
print 'C costs', timeC, 'the result is', a

start = time.time()
b = list("abcd")
b.reverse()
b = ''.join(b)
timePython = time.time()-start
print 'Python costs', timePython, 'the result is', b

运行结果

可以看出,python也不是绝对比C慢嘛,还要看情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/117352.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot学习篇(三)之通用mapper的使用(oracle版)

Spring Boot学习篇(三)之通用mapper的使用(oracle版) 1 配置pom.xml <parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.7.2</version></parent> <de…

概念辨析|电子文件单轨制与电子档案单套制

电子文件单轨制和电子档案单套制是档案业务中的重要概念&#xff0c;在建设数字中国的时代背景下&#xff0c;厘清“套”与“轨”的区别和联系是革新档案工作的基础 产生背景 随着信息技术的不断发展和政务信息化的持续推进&#xff0c;电子文件逐渐在业务工作中大量出现&…

从工具到实践:如何在GitHub上保障开源项目安全?

1998年&#xff0c;Christine Peterson创造了 “开源软件”这个词。她解释道&#xff1a;“这是刻意为之&#xff0c;为了让其他人更容易理解这个领域”。同年&#xff0c;O’Reilly组织了首届“开源峰会”。 开源软件受到更多人青睐原因在于&#xff0c;用户对软件拥有更多的…

【圣诞节】简单代码实现圣诞树|圣诞贺卡 | 快来为心爱的她送上专属的圣诞礼物叭~

圣诞节马上就要到了&#xff0c;不知道给自己喜欢的人准备什么样的惊喜吗&#xff1f;作为一名程序员&#xff0c;当然是用编程制作专属于她or他的圣诞树&#xff01; 目录 &#x1f384;圣诞树 ✨3D圣诞树 代码块 打开方式 修改位置 效果展示 ✨音乐律动圣诞树 代码块…

详解 Vue 过渡 transition 动画 animation 并结合第三方库 animation.css 和 gsap

transition vue过渡组件 标签自带类名 触发时机默认类名 自定义类名 <transition name"xxx"> 自定义行内式类名 方便结合第三方库 transition 钩子 接收参数el enter 和leave 第二个参数 done 可以 决定 after-enter after-leave 的 周期内的执行时机 v-…

YonBuilder移动开发平台 AVM框架 封装虚拟数字键盘组件

AVM&#xff08;Application-View-Model&#xff09;前端组件化开发模式基于标准Web Components组件化思想&#xff0c;提供包含虚拟DOM和Runtime的编程框架avm.js以及多端统一编译工具&#xff0c;完全兼容Web Components标准&#xff0c;同时兼容Vue和React语法糖编写代码&am…

Codeforces Round #697 (Div. 3) E. Advertising Agency

翻译&#xff1a; 玛莎在一家广告公司工作。为了推广新品牌&#xff0c;她想和一些博主签约。玛莎总共有&#x1d45b;个不同的博主。编号为&#x1d456;的博主拥有&#x1d44e;&#x1d456;名粉丝。 由于玛莎的预算有限&#xff0c;她只能与&#x1d458;不同的博主签约。…

LeetCode动态规划—跳跃游戏从跳到头到跳最少下跳到头(45、55)

跳跃游戏跳跃游戏跳跃游戏Ⅱ跳跃游戏 一个下标对应的值为3&#xff0c;那证明这个位置可以跳到前后3个位置的下标处。&#xff08;3均可达&#xff09; 如果依次遍历完这个数组&#xff0c;有下标在跳跃过程中最远位置仍然不可达&#xff0c;即证明无法到达最后一个位置。 可以…

js实现九宫格抽奖功能

分享一下js的九宫格抽奖功能 首先是html部分&#xff1a; <div class"box"><div class"div2">短裙</div><div class"div3">口红</div><div class"div4">草莓</div><div class"div…

【vue】控制台中如何移除数组arr中的值?如何给数组arr中放值?

移除数组arr属性中的值&#xff1a;vm.arr.shift() 新增数组arr属性中的值&#xff1a;vm.arr.push(‘属性值’) 移除atguigu3样式后效果&#xff1a; 向数组arr中添加样式值后效果&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta…

FlinkSql开窗实例:消费kafka写入文本

前言 以前写Flink从kafka入hdfs因为业务需求和老版本缘故都是自定义BucketSink入动态目录中&#xff0c;对于简单的需求可以直接用Flink SQL API进行输出。Flink版本1.13.1。 Flink官网示例 准备 本地下载个kafka&#xff08;单机即可&#xff09;&#xff0c;新建个桌面目…

Unreal 读写自定义配置文件

基础 首先需要自定义一个继承自UObject的类&#xff0c;UCLASS加上config标志 UCLASS(config MyClass) class UMyClass: public UObject将想要和配置文件交互的属性&#xff0c;UFUNCTION加上Config标志 UPROPERTY(Config, EditAnywhere) float TestP;之后只要配置文件内存…

【日常系列】LeetCode《21·综合应用3》

数据规模->时间复杂度 <10^4 &#x1f62e;(n^2) <10^7:o(nlogn) <10^8:o(n) 10^8<:o(logn),o(1) 内容 lc 217 &#xff1a;存在重复元素 https://leetcode.cn/problems/contains-duplicate/ 提示&#xff1a; 1 < nums.length < 10^5 -10^9 < nums[…

Python基础教程(2)——列表、元组、字典、集合、斐波纳契数列、end 关键字、条件控制、循环语句

1.列表 &#xff08;1&#xff09;删除列表的元素 list [Google, Runoob, 1997, 2000] print ("原始列表 : ", list) del list[2] print ("删除第三个元素 : ", list)&#xff08;2&#xff09;Python列表脚本操作符 &#xff08;3&#xff09;嵌套列表…

Arco 属性

文章目录Arco介绍1. 简介1.1 背景1.2 运行环境1.3 浏览器兼容性2. 设计价值观2.1 清晰2.2 一致2.3 韵律2.4 开放3. 设计原则3.1 及时反馈3.2 贴近现实3.3 系统一致性3.4 防止错误发生3.5 遵从习惯3.6 突出重点3.7 错误帮助3.8 人性化帮助4. 界面总体风格4.1 页面风格4.1.1 主色…

知识答题小程序如何制作_分享微信答题抽奖小程序制作步骤

知识答题小程序如何制作&#xff1f;现在越来越多的企业和组织逐步进行各种获奖知识问答小程序。那么&#xff0c;如何制作一个答题小程序呢&#xff1f;今天&#xff0c;我们一起来看看~需要的老板不要走开哦~既然点进来了&#xff0c;那就请各位老板耐心看到最后吧~怎么做一个…

JDBC如何破坏双亲委派机制

JDBC的注册会涉及到java spi机制&#xff0c;即Service Provideer Interface&#xff0c;主要应用于厂商自定义组件或插件中&#xff1b;简单说就是java来定义接口规则和方法&#xff0c;厂商实现具体逻辑&#xff0c;每家厂商根据自己产品实现的逻辑肯定不相同&#xff0c;但上…

数据库查询语句-详细篇

今天来梳理一下数据库的一些查询语句&#xff0c;做软件/移动端/电脑端&#xff0c;开发程序时必然离不开数据库的设计以及查询&#xff1b; 一&#xff1a;具体的代码如下展示&#xff1a; 1.查询数据库指定表的所有信息 select * from uploadimagecode;2.查询当前数据表部…

说说PPT的“只读模式”和“限制编辑”有何区别

对PPT的内容进行保护&#xff0c;使其不能随意编辑&#xff0c;防止意外更改&#xff0c;我们可以将PPT设置成无法编辑、无法改动的“保护模式”。 设置“保护模式”&#xff0c;一般我们都会想到【限制编辑】模式&#xff0c;但在设置的时候&#xff0c;会发现PPT里&#xff…

毕业半年年终总结

毕业半年年终总结 如果说2021年主要的内容是求职和实习 那么2022年一年主要的内容便是毕业和工作 匆匆忙忙 本科毕业了 6月份的时候参加完毕业答辩&#xff0c;也就顺利的毕业了 实际上中途也有过一些插曲&#xff0c;比如毕业设计是制作某某管理系统&#xff0c;基本上所有…