python封装c++接口_第13篇:Cython封装C++类接口

2023-11-18

本篇我们将详细讲解Cython封装C++代码,并如何调用它们,在进行这个主题前,我们需要需要先讲解一下这些概念定义文件

实现文件

cimport 和import语句的区别

Cython还允许我们将项目分解为几个模块。 它完全支持import语句,其含义与Python中的含义相同。这使我们可以在运行时访问在外部纯Python模块中定义的Python对象或在其他扩展模块中定义的Python可访问对象.

Cython文件类型

Cython提供了三种文件类型,可帮助组织项目的Cython特定部分和C级部分。

实现文件(implementation file):到目前为止,我们一直在使用扩展名为.pyx的Cython源文件.

定义文件(Declaration File):其扩展名为.pxd,包含任何C级别可以被其他Cython模块公开访问的如下表项。C类型声明ctypedef、struct、union或enum

外部C或C++库的声明

cdef和cpdef模块级函数的声明

cdef class 扩展类型的声明

扩展类型的cdef属性

cdef和cpdef方法的声明

C级内联函数和方法的实现

但定义文件不能包含如下代码Python或非内联C函数或方法的实现

Python类定义

IF或DEF宏之外的可执行Python代码

包含文件(Include File) ,扩展名为.pxi。

cimport语句

cimport语句能够将.pyx文件、.pxd文件和.pxi文件之间的代码相互关联;使各个Cython源代码构造更大的Cython项目。有了cimport语句和三种文件类型,我们就可以在不影响性能的情况下有效地组织Cython项目

我们通过一个示例来解析一下,比如我们下面有一个关于Fruit扩展类的类定义,以及一些辅助函数的声明,它们位于cy_fruit.pxd中,

#cython:language_level=3

cdef class Fruit(object):

cdef:

readonly str name

public double qty

readonly double price

cpdef double amount(self)

#end-class

cdef list shop_cart(list itemList ,Fruit item)

cdef double payment(list)

cpdef void display_fruit(Fruit)

在pxd文件中Fruit类定义仅由类属性声明和和类方法的声明,类方法的声明只是包含类方法的签名,并没有类方法的实现代码,这些一切和C++的头文件定义都非常相似,但唯一不同的是Cython并不允许在定义文件中存在类方法的具体实,而在C++中这是允许的。

我们有了之前的定义文件,在对应的实现文件中cy_fruit.pyx,我们需要通过cimport语句在实现文件中加载c_fruit.pxd文件中声明类定义和辅助函数声明,即语句from cy_fruit cimport Fruit,shop_cart,payment,并且要实现它们,如果你们有C/C++编程的概念,这是很好理解的。因为我们定义文件是用于编译时实现文件访问它们,Cython提供专用的cimport语句导入.pxd文件或.pyx文件,如下代码所示

#cython:language_level=3

from cy_fruit cimport Fruit,shop_cart,payment

cdef class Fruit(object):

'''Fruit Type'''

def __cinit__(self,str nm,double qt,double pc):

self.name=nm

self.qty=qt

self.price=pc

cpdef double amount(self):

return self.qty*self.price

def __repr__(self):

return "name:{},qty:{},price:{}".format(

self.name,self.qty,self.price)

#end-class

cdef list shop_cart(list itemList ,Fruit item):

if item.name!='' and item.qty:

itemList.append(item)

return itemList

cdef double payment(list itemList):

cdef double total=0.0

if len(itemList[0]):

for item in itemList[0]:

total+=item.amount()

return total

cpdef void display_fruit(Fruit obj):

print(obj)

因为cimport语句与import语句的语法非常相似,我们还可以这样导入.pxd文件,当我们要实现类中的方法,要加上.pxd文件的名称cy_fruit,跟Python的import一样,我们称cy_fruit这样名称为命名空间,

cimport cy_fruit

....

cdef class cy_fruit.Fruit(object):

.....

cpdef double amount(self):

return self.qty*self.price

#end-class

那么在实现文件中访问定义文件访问Cython扩展类定义,需要这样的格式[命名空间].[类名称],例如:cy_fruit.Fruit

同样,我们也可以导入pxd文件时,cimport语句还可以使用as子句给命名空间设定别名,例如

cimport cy_fruit as cyf

....

cdef class cyf.Fruit(object):

.....

cpdef double amount(self):

return self.qty*self.price

#end-class

同样,我们还可以使用as子句,为导入的具体的类名称,函数名称设定别名

from cy_fruit cimport Fruit as Fru,

shop_cart as cart,

payment as pay

....

cimport和import的区别import语句用于运行时导入Python模块(含Cython已编译的扩展模块)/包。尝试导入Cython的cdef关键字声明的数据类型:扩展类,C类型的变量,或函数声明,会产生编译时错误。

import语句可以导入cpdef关键声明的函数或类方法,因为cpdef关键字修饰的函数或类方法会在Cython编译器编译扩展模块时,生成该类方法或函数的Python版本包装函数(或类方法的包装函数)

cimport语句用于编译时导入Cython定义文件或Cython实现文件,若尝试导入Python级别的对象,变量,函数会产生编译时错误。

一个简单的例子能够说明import和cimport之间的差异,我们看看下面的python脚本app.py

#!/usr/bin/python3

import pyximport

pyximport.install()

from cy_fruit import Fruit

from cy_fruit import display_fruit

if __name__=='__main__':

f=Fruit("apple",52,33

display_fruit(f)

在app.py中我们通过只能使用import语句导入cy_fruit模块中的Fruit类,同时也能通过import语句导入cpdef关键字声明的函数。在Python上下文中,Python解释器只能识别import语句,无法理解cimport语句。

另外,import语句尝试从已编译的Cython扩展模块中导入cdef关键字声明的函数或变量会提示ImportError错误,因为Python代码是无法访问Cython扩展模块中任何C级别私有属性或cdef声明的函数

cdef extern from语句块

定义文件允许我们使用cdef extern from语句块加载Cython代码以外的纯C/C++代码,并且通过Cython代码进行封装,这样的好处是能够将外部的C/C++的代码能够在Cython源代码中重用

我们对前面的示例进一步扩展,希望按照货币格式打印Fruit对象的价格(price)和销售总金额(amount),这里会用到C++写的MoneyFormator类,该类用于对传入的数字字面量进行货币格式化。

以下是MoneyFormator类接口定义文件,定义在一个叫currency.hh的头文件中

#ifndef MONEYFORMATOR_H#define MONEYFORMATOR_H#include #include #include #include #include

namespace ynutil{

class MoneyFormator{

public:

MoneyFormator();

MoneyFormator(const char*);

~MoneyFormator();

std::string str(double);

private:

std::locale loc;

const std::money_put& mnp;

std::ostringstream os;

std::ostreambuf_iterator> iterator;

};

}

#endif

MoneyFormator类实现文件,定义在currency.cpp文件中。

#include "currency.hh"

namespace ynutil {

MoneyFormator::MoneyFormator()

:loc("zh_CN.UTF-8"),

mnp(std::use_facet<:money_put>>(loc)),

iterator(os)

{

os.imbue(loc);

os.setf(std::ios_base::showbase);

}

MoneyFormator::MoneyFormator(const char* localName)

:loc(localName),

mnp(std::use_facet<:money_put>>(loc)),

iterator(os)

{

os.imbue(loc);

os.setf(std::ios_base::showbase);

}

MoneyFormator::~MoneyFormator(){}

std::string MoneyFormator::str(double value){

//清理之前遗留的字符流 os.str("");

mnp.put(iterator,false,os,' ',value*100.0);

return os.str();

}

}

Cython封装C++代码

Cython包装C ++类的过程与包装C结构体的过程非常相似

首先,我们需要创建一个定义文件,这里我们命名为currency.pxd,在定义文件中使用cdef external from语句块从currency.hh类定义文件加载MoneyFormator类定义细节。这里还使用namespace关键字为Cython的类定义文件currency.pxd声明了命名空间ynutil,和C++的currency.hh的类定义文件的namespace是一一对应的。

cdef extern from "currency.hh" namespace "ynutil":

接下来,使用cppclass关键字声明Cython扩展类MoneyFormator,这是告诉Cython编译器正在封装的外部代码是C++代码,并且Cython类的名称和C++版本的MoneyFormator类名称必须一致。完整代码如下

#cython:language_level=3

cdef extern from "currency.cpp":

pass

from libcpp.string cimport string

cdef extern from "currency.hh" namespace "ynutil":

cdef cppclass MoneyFormator:

MoneyFormator() except +

MoneyFormator(const char*) except+

string str(double)

上面示例是一个有效的Cython类声明,有如下细节需要知道的第一条语句cdef extern from "currency.cpp"这条语句其实就是等价于C++代码中的

#include "currency.cpp"

就是告知Cython编译器将MoneyFormator的类实现代码加载到currency.pxd的定义文件中。并且currency.cpp的类定义细节会被pxd文件中的Cython类定义MoneyFormator使用

Cython类定义必须嵌套在和C++头文件关联的cdef extern from 语句块中

Cython类定义内部声明了允许公开给Python外部代码的类方法。例如默认的构造函数、自定义构造函数、str方法这些声明都是和C++版本的类定义是一一对应的

构造函数的声明追加“ except +”,这是Cython封装C++代码的特殊语法。 如果C ++代码或初始内存分配由于故障而引发异常,这将使Cython可以安全地引发适当的Python异常(请参见下文)。 没有此声明,Cython将不会处理源自构造函数的C ++异常。

上面的Cython封装C++实现的类MoneyFormator,其实就设计三个源代码文件,Cython代码不需要理会C++代码中的细节

在.pxd文件中的Cython类定义中,所谓的封装就是,程序员可以选择性地以相同的类方法名称和属性名称以Cython的语法将对应的C++版本的类方法和属性逐个声明一次。本示例中,我们并没有对C++中版本中欧给你的MoneyFormat的私有属性逐个声明一篇,

class MoneyFormator{

....

private:

std::locale loc;

const std::money_put& mnp;

std::ostringstream os;

std::ostreambuf_iterator> iterator;

};

因为没必要,首先Cython并不完全支持C++ 标准库中的所有内置扩展数据类型和函数,例如上面C++版本中的std::locale,和std::money_put类模板,这些C++类型在Cython的libcpp目录内预设的C++封装的定义文件中是不存在的类似的locale.pxd的声明,我们可以查看Cython扩展中的include/libcpp目录下,可以得到验证

除非你自行封装对应C++的类型到对应Cython的类声明,以扩展libcpp目录下的数据类型对Cython语法的支援,但默认的libcpp目录下对C++的类型封装已经足够我们编程需要了。

编译扩展模块

我们之前以定义文件的形式对C++代码进行了Cython形式的封装,要将封装后的Cython代码给外部的Python代码调用,我们需要在创建一个实现文件,我们这里将该实现文件命名为money.pyx,Cython允许我们在实现文件中通过不同编程模式给Python代码提供特定Python接口,如下图所示面向过程:通过cpdef函数,而该函数内部调用被封装的C++代码所公开的接口,而外部Python代码调用该cpdef函数的Python版本的包装函数。

面向对象:通过Cython扩展类对C++代码进行调用,而Cython扩展类本身可以给Python代码调用的。

而我们本篇会先介绍面向过程的,下面是一个具体的例子,我们采用了一个cpdef函数,语法上我不想多说什么,语法上的难点前面6篇Cython教程已经说的很清楚

# distutils: language=c++

#cython:language_level=3

from currency cimport MoneyFormator

from libcpp.string cimport string

cpdef string money_format(str localName,double n):

'''重堆中为MoneyFormator类分配内存'''

cdef MoneyFormator* mon

try:

if localName=='' or localName==None:

mon=new MoneyFormator()

else:

mon=new MoneyFormator(localName[0].encode('utf-8'))

return mon.str(n)

except Exception as e:

print(e)

finally:

del mon

这里值得一提的是代码中的# distutils: language = c++,会告知Cython编译器将Cython代码先解析为C++代码,进而再编译成可执行模块。因为赋予了C++代码的语义,因此我们能够在Cython代码中使用new操作符为Cython扩展类MoneyFormator,在堆中内存中实例化MoneyFormator,在cdef函数最后,我们需要显式释放内存。

当然,如果你希望在栈上实例化MoneyFormator的话,可以使用在cpdef函数内部声明局部变量,即如下代码

cdef MoneyFormator fmt=MoneyFormator()

编译上面的代码,笔者更喜欢使用cythonize命令,如下所示

cythonize -i -a money.pyx

import我们自己的C扩展模块,如下图所示,笔者

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python封装c++接口_第13篇:Cython封装C++类接口 的相关文章

  • vue生命周期mounted和activated使用、踩坑

    activated 说到activated不得不提到keep alive 你切换出去又切出来会调用到它 你可以理解为生命周期钩子函数 用法也一样 mounted 指的是实例被挂载后调用 如果没有keep alive每次切回来该组件都会触发一
  • 理解Spring的AOP和Ioc/DI就这么简单

    一 什么叫Ioc DI Ioc Inversion of Control 控制反转 DI Dependency Injection 依赖注入 其实这两个概念本质上是没有区别的 那我们先来看看什么叫做Ioc 假设这么一个场景 在A类中调用B类
  • 华硕重装系统键盘灯失效 =>重装ATK驱动

    1 点击网站华硕服务与支持 https www asus com cn support 2 输出笔记本型号 选择产品 3 下载驱动 3 1选择驱动程序和工具软件 3 2选择操作系统 3 3找到ATK驱动并且下载 4 安装驱动 4 1安装AT
  • 如何使用随机数实现自动发扑克牌?

    学习不止 问答不止 一 粉丝问题 二 相关函数说明 1 函数说明 产生随机数的方法很多 常用的是rand srand 来看一下这2个函数的定义 SYNOPSIS include
  • 如何导入符号 emdk?

    我在最新的 Android Studio 中创建了一个新的 android projekt 我想导入和使用 Symbol EMDK 包 虽然我像这样放入 gradle implementation com symbol emdk 9 1 1
  • 一文带您了解软件多租户技术架构

    1 多租户技术概述 随着近几年云计算技术的不断发展和成熟 云计算多租户技术在 SaaS 服务领域获得得快速的发展和广泛的应用 基于多租户技术的业务平台首先要保证不同租户业务的隔离 业务隔离主要包括下面 2 个方面 物理隔离 租户开展业务所依
  • 字符串的字体和显示 (3)

    安卓有三种字符串 String String Array Quantity String Plurals String和String Array容易理解 一个是字符串 一个是字符串数组 通过 String planets res getSt
  • Qt做发布版,解决声音和图片、中文字体乱码问题

    前些天做Qt发布版 发现居然不显示图片 后来才发现原来还有图片的库没加 找找吧 去qt的安装包 我装在了F盘 在F盘F QT qt plugins 找到了plugins 这里面有个 imageformats是图片的库 里面有jpg gif等
  • 谷歌开源代码评审规范:好坏代码应该这样来判断

    谷歌开源了一套代码评审 Code Review 规范 它是谷歌一套通用的工程实战指南 几乎涵盖了所有编程语言与各种类型的项目 这个规范代表了谷歌长期发展以来最佳实战经验的集合 谷歌表示希望开源项目或其他组织能够从这套规范中受益 代码评审 也
  • Docker学习:Docker核心命令

    前言 本讲是从Docker系列讲解课程 单独抽离出来的一个小节 重点介绍八大核心命令和一些常用的辅助命令 比如inspect logs push commit等 如果你想 通过部署Tomcat容器 从查找镜像 到拉取 到运行 最后到移除 来
  • sql server - 将sqlserver安装到虚拟机内

    目录 1 安装 打开虚拟机 1 1 打开vmware 1 2 安装虚拟系统到vmware Windows Server 2016 2 安装SQL server 2014 2 1 把SQL server下载并上传到虚拟机 2 2 安装与配置
  • C++ 虚函数表解析

    C 虚函数表解析 陈皓 http blog csdn net haoel 前言 C 中的虚函数的作用主要是实现了多态的机制 关于多态 简而言之就是用父类型别的指针指向其子类的实例 然后通过父类的指针调用实际子类的成员函数 这种技术可以让父类
  • Web前端学习:jQuery基础 · 小终结【异步处理AJAX】

    目录 一 AJAX介绍 AJAX处理过程 二 AJAX请求 代码演示 案例一 获取txt文本内容 通过页面窗口弹出 案例二 返回json数据 一 AJAX介绍 ajax技术的目的是让javascript发送http请求 与后台通信 获取数据
  • Flask框架-重定向与错误

    在Web服务访问时并不总是能够返回正确的结果 当用户访问了错误的URL 或者传输了错误的请求参数 Web服务就需要返回相关的错误信息进行提示 Flask中针对错误请求的场景提供了相关的API 包括标准错误的响应和标准重定向的响应处理 在We
  • 江波龙深化存储技术优势 紧密结合物联网应用需求创新

    转自 http www chinaflashmarket com Producer Netcom News 142127 云计算 大数据以及移动互联网时代下 全球存储容量以爆发式的速度在增长 根据市场调研机构预测 2020年全球存储容量将从
  • servlet编程会话管理技术

    1 会话管理 浏览器与服务器之间会话过程中产生的会话数据 Cookie特点 1 会话数据放在浏览器端 2 数据类型只能string 而且有大小限制的 3 相对数据存放不安全 Session特点 1 会话数据放在服务器端 服务
  • MES的数据采集方式

    为实现生产车间现场数据的采集 制造业MES系统数据采集方法有手工录入方式和自动化提取采集两大类 主要有以下几类 一 手动方式 1 手工方式 操作员或编程员在MES系统控制面板上 输入特定的触发程序 经DNC服务器的自动翻译 就可得到机床端的
  • Jenkins集成Sonar与Gitlab代码质量检测

    前提默认 安装docker19 与docker compose 安装Jenkins 1 docker compose yaml配置 version 3 services jenkins network mode host 镜像 image
  • 3、Java的If语句与For循环

    一 语句 条件语句 根据不同的条件 执行不同的语句 if if else if else if if else if else if else switch 循环语句 重复执行某些动作 for while do while 1 1 if语句

随机推荐

  • 深度学习算法面试常问问题(三)

    pooling层是如何进行反向传播的 average pooling 在前向传播中 就是把一个patch的值取平均传递给下一层的一个像素 因此 在反向传播中 就是把某个像素的值平均分成n份 分配给上一层 max pooling 在前向传播中
  • 用Odoo创建一个网站

    原贴地址 https www odoo com documentation 10 0 howtos website html 声明 这篇指导假设你有python的知识并安装了Odoo 请注意文件的目录结构 本文的目录结构与原文不同 创建一个
  • 快速打开CMD的几个方法

    1 在开始菜单里面找CMD EXE 很快的哟 2 在资源管理器的地址栏直接键入CMD按回车也能启动CMD 3 资源管理器 按住Shift点右键也能召唤CMD哦 选 在此处打开命令窗口 就行了
  • squirrel-foundation状态机的使用细节

    上一篇文章介绍了stateless4j spring statemachine以及squirrel foundation三款状态机引擎的实现原理 以及我为何选择squirrel foundation作为解决方案 本文主要介绍一下项目中如何使
  • sqlloader出现SQL*Loader-704和ORA-12154的错误

    1 错误描述 生成的sqlloder各个文件完好 权限也具备 但是就是导入oracle数据库的时候报错 错误为 SQL Loader 704 Internal error ulconnect OCIServerAttach 0 ORA 12
  • vue3+ts实现todolist功能

    先看一下实现效果 可以看到内部实现的内容有enter输入 单项删除 全选 以及删除选中项等功能 具体在实现前需要常见有ts的vue3项目 项目创建 具体项目创建 就是 vue create 项目名称 在创建后 选择的时候有vue2和vue3
  • CUDA10.0官方文档的翻译与学习之介绍

    背景 从这次开始 我将用数篇博客来分享前一阵我对CUDA10 0官方文档之编程指南的翻译与学习的笔记 由于内容非常多 我将每一章单独分享出来 可能有地方翻译得词不达意 所以建议大家参考原文 https docs nvidia com cud
  • 多线程实现事务回滚

    多线程实现事务回滚 特别说明CountDownLatch CountDownLatch的用法 CountDownLatch num 简单说明 主线程 mainThreadLatch await 和mainThreadLatch countD
  • 剑指 offer第62题-圆圈中最后剩下的数

    让小朋友们围成一个大圈 然后 随机指定一个数 m 让编号为 0 的小朋友开始报数 每次喊到 m 1 的那个小朋友要出列唱首歌 然后可以在礼品箱中任意的挑选礼物 并且不再回到圈中 从他的下一个小朋友开始 继续 0 m 1 报数 这样下去 直到
  • Proc批量处理需要注意的问题

    ProC中批量读取游标中的数据的时候 需要注意 最后一次批量读取游标中的数据的时候 数据被取到HostArray中 同时sqlca sqlcode被置为1403 NO DATA FOUND 如果在fetch后立即判断sqlca sqlcod
  • 隐藏selenium的特征

    1 chromedriver exe中的 cdc asdjflasutopfhvcZLmcfl 特征 cdc 是chromedriver exe的一个特征之一 很多网站会通过检测是否有这个特征来判断是否是selenium 解决方案 wind
  • centos7安装mate

    http www 45drives com wiki index php Installing MATE on CentOS 7 Note This guide assumes you have a CentOS 7 minimal ins
  • Python基础知识之5

    Python基础知识之5 文件操作 1 文件的打开与关闭 文件打开 在python 使用open函数 可以打开一个已经存在的文件 或者创建一个新文件 基本格式 open 文件名 访问模式 实例如下 f open test txt w 文件关
  • 关于soot静态分析的学习(一)

    本文中关于soot的研究使用 仅代表本人理解程度 因本人为0基础 所以如有出错 欢迎指出 一 soot是什么 Soot Java静态分析框架 其实Soot最开始设计的时候 主要目的就是为了对Java字节码程序进行优化 这里的优化就是指执行效
  • 【Qt】Qt事件系统

    00 目录 文章目录 00 目录 01 Qt事件系统概述 02 事件如何传递 03 事件类型 04 事件处理 05 事件过滤器 06 事件发送 附录 01 Qt事件系统概述 Qt 5 12 Qt Core The Event System
  • vb和asp如何用remote访问远程数据库

    访问远程数据库的情况有以下几种 1 访问远程数据库的access数据库2 访问远程mssql数据库或oracle等其他关系数据库 但是数据库通信端口被防火墙阻挡或其他网络原因造成无法使用该端口 本文仅在windows2000 advance
  • Sort List

    Sort a linked list in O n log n time using constant space complexity 题目要求用 O n log n 的时间复杂度和常数的空间复杂度来进行链表排序 O nlogn 的排序算
  • 怎么画出好看的神经网络图,神经元怎么画简笔画

    神经网络具体是什么 神经网络由大量的神经元相互连接而成 每个神经元接受线性组合的输入后 最开始只是简单的线性加权 后来给每个神经元加上了非线性的激活函数 从而进行非线性变换后输出 每两个神经元之间的连接代表加权值 称之为权重 weight
  • 使用AFNetworking(二)

    文章目录 HIHTTPSessionManager HIHTTPRequestManager HIHTTPRequest 注意事项 写代码没有什么具体的规范和方式 只是哪个更加合理罢了 老的项目HTTP请求使用的是AFNetworking找
  • python封装c++接口_第13篇:Cython封装C++类接口

    本篇我们将详细讲解Cython封装C 代码 并如何调用它们 在进行这个主题前 我们需要需要先讲解一下这些概念定义文件 实现文件 cimport 和import语句的区别 Cython还允许我们将项目分解为几个模块 它完全支持import语句