跳转到主要内容

Swimport是一个Python库,用于简化自动化生成SWIG接口文件的过程,这个过程可能包含一些曲折和容易出错的部分。

项目描述

Swimport

关于

Swimport是一个Python库,用于简化自动化生成Python的SWIG接口文件的过程。

Swimport是一个高级工具,用户应具备Python和C++的基本以上知识。同时,还需要阅读SWIG文档的第1、2、5-13和34章。

目前,swimport不是一个,也不是设计为自动将.h文件转换为.i文件的工具。大多数使用都需要对输出文件进行额外的用户配置。建议用户将输出文件导入到包含swimport无法处理或未设计处理的部分的用户编写的文件中。

安装和需求

Swimport需要CppHeaderParser模块。除此之外,安装方式与任何其他Python库相同(Swimport是纯Python库)。

测试和示例可能有一些额外的要求。

用法

摘要

  • 该库读取C++头文件(.h),并创建一个SWIG接口文件(.i)。当与SWIG一起构建时,使用源文件和(可选)头文件,可以编译成利用C++代码的Python模块。

结构

  • 首先,从头文件路径(.h)创建源对象,它们处理源文件中C++对象的读取和解析。
  • 然后,创建SwimRules来处理C++对象的导入。SwimRule由两个不同的对象组成
    • 一个触发器,它决定C++对象是否会被规则导入。
    • 一个行为,它处理有效的C++对象,并将它们包装在Swimporting对象中。
    • 每种C++对象类型(变量、方法等,见下文)都有其自己的触发器和行为类型,尽管可以使用自定义类(见下文)。
  • SwimRule处理源文件,检查哪些cpp对象通过触发器,并为这些对象返回Swimporting对象。
  • Swimporting对象是针对特定C++对象和导入行为的专用函数。
  • swimport的使用围绕Swim对象。Swim接收Swimporting对象,并为自己添加行。
  • 最后,Swim对象将它的行写入输出文件。

对象类型

方法

导入方法需要特殊规则来处理参数。它们的行为方式类似,每个都有触发器和行为。库中内置了常见的默认行为和规则(默认方法行为,见下文)。

容器

容器是struct、class和union的统称。有两种导入容器的方式。

触发器-行为

容器支持基本的触发器-行为协议。注意,此协议仅应用于简单的c结构。没有构造函数、方法或任何私有或静态成员。

ContainerSwim

ContainerSwim对象作为一个伪Swim对象,处理方法、静态成员和扩展方法(在C++和Python端)。请参阅示例37-43以获取使用示例。

枚举

可以使用SwimRules正常导入枚举。注意,枚举值在Python中被视为全局变量。

Python枚举行为

存在一种特殊的行为,用于将C++枚举包装成Python枚举类。使其可用作Python枚举。

变量和常量

可以使用SwimRules正常导入变量。使用#define声明的常量不受支持。注意:在C和Python之间共享可变值作为变量很棘手。请参阅官方SWIG文档以获取解释,但一般来说:所有在运行时可能发生变化的C变量都必须通过库中的特殊cvar对象来访问。

GetSet行为

为了避免上述问题,存在一种针对变量的专用行为,它生成C变量的getter和setter方法。

typedefs

可以使用SwimRules正常导入typedefs。在需要导入多个源中的相互依赖的typedefs的情况下,可以使用TypeDefAggregate对象来聚合typedefs并按适当顺序排序。

类型

类型是一个虚拟的C++对象。类型可以用于将复杂的C++类型转换为Python对象。必须直接创建类型的Swimporting对象。

Typemaps

使用类型需要编写SWIG类型映射。最常见的方法是将cpp函数的体放入类型映射中,让Swimport自动在其周围包装一个函数,并在类型映射中调用它。还有其他创建类型映射的方法,最著名的是BuiltinTypemap。

池是专门用于添加到输出代码中的代码片段。一些池可以直接使用,而另一些则需要参数。有关所有池的信息,请参阅 swimport.pools.__doc__

直接插入

用户还可以使用Swim的add_*方法将.i代码直接插入输出文件。

其他特性

重复导入阻塞

Swim对象会记录它们已接受的所有Swimporting对象,并拒绝重复导入相同的对象。这意味着在应用规则时,用户可以先应用最狭窄的规则,然后应用更宽泛的规则,而无需担心重复或排除已导入的对象。

Python代码模板

SWIG具有作用域,允许直接将Python代码注入到输出文件中。尽管这些注入很有用,但它们的限制性较大,不提供类似于函数名称和返回值的宏。因此,用作MethodBehaviour参数(如prepend_pythonappend_python)的Python代码用作模板,使用形式为$...${...}的宏。替换遵循以下规则

  • 特殊规则$$$字符的转义。
  • 形式为$<identifier>的宏计算为预设值
    • $retvar:包含函数输出的变量。
  • 形式为${identifier}的宏计算为导入方法的Python代码,其中变量method表示导入的方法。
  • 所有其他$字符的使用都是无效的
behaviour = Method.Behaviour(prepend_python='print("entered ${method.name}")',
                             append_python='print("exiting ${method.name}, result: $retval")')

自定义行为和触发器

用户可以创建自己的行为和触发器类,以下有示例。在这样做的时候,重要的是要子类化swimport.Triggerswimport.Behaviour或它们的特化。

from swimport import Method

class VoidTrigger(Method.Trigger):  # a trigger class that only matches methods that return void
    def is_valid(self, rule, obj: Method):
        return super().is_valid(rule, obj)\
            and obj.return_type == 'void'  # todo account for dll names

规则断言

应用一系列swimporting(如方法规则的结果)时,它返回一个整数,指示接受了多少个swimporting。这些可以用来断言导入的对象

assert swim(method_rule_foo(src)) == 1  # assert that only one object was imported
assert 'foo' in swim(method_rule_foo(src))  # assert that an object of name 'foo' was imported
assert 'foo' in swim(method_rule_foo(src)) == 1  # assert that only an object of name 'foo' was imported

重要的是要提到,如果脚本使用(很少使用)的-O标志运行,则断言以及其内容将被忽略。为了解决这个问题,建议使用第三方库,如ensure

默认参数规则

Swimport默认使用以下规则来分类方法参数。这些可以通过更改单个MethodBehaviourparameter_rules或使用MethodBehaviour.default_parameter_rules更改默认值来更改。以下组规则按顺序考虑

前缀 类型 操作
AIO_ ANY 任何秩的内置numpy数组[^1][^3]
A_ 任何非const指针或引用 1D numpy输出数组[^1][^3]
A_ 任何const指针或引用 1D numpy输入数组[^1][^3]
BF_ 非const指针或引用 需要释放的输出字节数组[^1][^3]
B_ 非const指针或引用 输出字节数组[^1][^3]
B_ const指针或引用 输入缓冲区[^1][^3]
SF_ 非const char指针指针或引用[^2] 需要释放的输出C字符串[^3]
ANY const char指针[^2] C字符串
IO_ ANY 作为输入和输出的参数[^4]
ANY 任何非const指针或引用 输出参数[^4]
ANY 任何const指针或引用 输入参数[^4]
ANY ANY 不执行任何操作

[^1]: 数组和缓冲区还消耗下一个参数作为输入。[^2]: 在任何使用char的地方,wchar_t也被接受。(见以下奇偶性)。[^3]: 需要特殊池[^4]: 需要导入到swimport的类型,见以下

将类型导入Swimport

高级swimport功能(如输入和输出参数,还包括以下列出的那些),要求swimport知道涉及的类型,包括标准转换函数和其他信息。这被称为swimporting,对于不同类型有多种实现方式。

类型Swimporting

这是导入类型的规范、最基本的方法(几乎所有其他导入方法都是通过TypeSwimporting包装的)。

容器、枚举和Typedef行为

大多数常见行为会自动导入容器类型及其指针类型。请注意,一些行为(如typedefs和基本枚举)只有在原始类型被导入时才会导入类型。

对于最常用的类型,有一些池可以轻松导入。

原语

pools.primitive映射导入所有原始类型(包括boolsize_twchar_t)。

void*

pools.void_ptr导入void*。

c字符串

pools.c_string导入C字符串(null-终止的char和wchar_t指针)。这还允许使用SF_前缀指定需要由调用者释放的C字符串。请注意,非const指针不能作为c++函数的输入。

std::string

pools.std_string导入std::stringstd::wstring

PyObject*

pools.pyObject导入原始PyObject*对象。

bool

pools.bool导入bool类型,允许它们接受任何Python对象,将其转换为其真实性值。请注意,如果与primitive池一起使用,则必须从原始导入中排除bool,使用pools.primitive(blacklist='bool')

None、Ellipsis和NotImplemented

pools.singletons导入在py_singletons.h中定义的特殊C++类型ellipsisNoneTypeNotImplementedPySingleton

切片

pools.slice导入在py_slice.h中定义的slice<...>结构。

复数

pools.complex导入std::complex类型。

元组

pools.tuple导入std::tuple<...>类型。它要求所有其子类型都正确导入。

输入可迭代对象

pools.input_iterable导入在py_iterable.h中定义的特殊模板py_iterable。接受此类型作为参数允许函数遍历Python对象。内部类型必须导入。此池自动应用于所有导入类型(除非明确禁用)。

可迭代对象

pools.iter导入可迭代类型。请参阅其文档以获取详细信息。此池自动应用于所有导入类型(除非明确禁用)。还有此池的特殊化,名为pools.listpools.setpools.frozensetpools.array(还需要数组长度),用于发出默认输出类型。

映射

pools.map导入映射类型(默认为std::unordered_map)。请参阅其文档以获取详细信息。

可调用

pools.callable导入用于C++输入的std::function<...>类型。它要求所有其子类型都正确导入。

注意:一些导入类型(最显著的是宽C字符串)有特殊的函数,在将Python对象转换为C++对象后必须调用(程序上称为to_cpp_post)。由于std::function调用者不知道这一点,使用这些类型会导致资源未释放。默认情况下,将此类类型用作pools.callable返回类型将引发错误。

缓冲区

pools.buffer导入参数签名为unsigned char *, size_t的缓冲区类型。请注意,缓冲区参数名称必须以"B_"或"BF_"开头

NumPy数组

pools.numpy_arrays 导入了参数签名中为 T *, size_t 的 numpy 数组类型,其中 T 是一个基本类型。请注意,nparray 参数名称必须以 "A_"、"AIO_" 或 "AF_" 开头(表示数组是新分配的,应由 numpy 数组释放)。

规则构建语法

触发器可以使用 >> 运算符合并成一个规则

from swimport import MethodBehaviour, MethodNameTrigger, TriggerBehaviourSwimRule

trigger = MethodNameTrigger('foo')
behaviour = MethodBehaviour()

assert (trigger >> behaviour) == TriggerBehaviourSwimRule(trigger, behaviour)

默认触发器和行为

许多 C++ 类型也有默认行为和触发器,以简化规则构建。在大多数情况下,默认触发器只接受与模式输入完全匹配的对象名称

from swimport import MethodBehaviour, MethodNameTrigger, Method

assert MethodNameTrigger('foo') == Method.Trigger('foo')
assert MethodBehaviour() == Method.Behaviour()

默认触发器甚至可以从参数自动构建

from swimport import Method

behaviour = Method.Behaviour()
assert ('foo' >> behaviour) == (Method.Triggers('foo') >> behaviour)

使用 ...,构建一个触发器,它接受行为可以处理的所有对象。

rule = ... >> behaviour

请注意,行为本身也可以以完全相同的方式使用。行为是接受适当类别中任何对象的规则。使用 ... >> behavior 语法对于为输出文件提供特定名称非常有用。

rule = (... >> behaviour).replace(name="my special rule")

复合触发器

触发器可以用在二元逻辑运算符中:| & ^ 来创建一个触发器,如果各个触发器适当地接受对象,则接受对象(例如:(a|b).is_valid(...) == a.is_valid(...) or b.is_valid(...)

同样,触发器也可以取反来创建否定((~a).is_valid(...) == not a.is_valid(...))。

触发器还可以组合成非蕴含关系((a > b) == a & (~b))。

示例

许多用法示例可以在 tests\examples 目录中找到。示例通常包含以下内容:

  • main.py:一个调用 swimport 库并构建 .i 文件的文件
  • src.cpp/src.h:要处理、编译和链接的 cpp 源文件
  • usage.py:一个显示结果 cpp/python 库用法的文件

请注意,某些示例可能需要额外的库才能运行。

已知问题/怪癖

  • 当导入 dll 时,函数名称不能被混淆(必须使用 extern "C")

    • 当使用 extern "C" 时,它必须具有作用域。
  • CppHeaderParser 中的一个错误会导致头文件以单行注释结束时会崩溃

  • SWIG 到 python 转换中的一个怪癖是,返回 void 的函数在 python 中返回 None。并且具有输出参数的函数只是简单地附加到原始输出上。这意味着如果函数有一个作为返回值的指针和一个输出参数,并且返回值为空,它将忽略返回值。这种行为与标准 SWIG 一致。解决方案是确保如果函数返回指针并具有输出参数,则确保返回值永远不会为空。

  • SWIG 和 CppHeaderParser 中的错误阻止枚举成为非整型

  • 输入字符串时,请注意,SWIG 接受包含空字符 \0 的字符串,这些字符串将被整体转换,但可能被误认为是大多数 C 代码中的字符串终止符。

  • CppHeaderParser 不能使用别名命令进行解析:using alias = oldname;

    数组

    是的,数组有很多注意事项,值得单独成章

    • 接受数组的函数会积极转换为所需的类型,包括向下转换和截断。因此,期望 int 数组的 C 函数将接受一个 python 浮点数列表,并将每个值单独截断。请参见示例 14 以查看其作用。
    • 默认情况下,char 类型的数组不可用,尽管可以使用 numpy_arrays 池的可选参数使用它们。请注意,接受的数组的 dtype 是 "S1",而不是 np 创建的 char 数组的默认 "U1"。使用 'signed char' 或 'unsigned char' 作为字节数组。请参见示例 15 以查看其作用。
    • 当数组用作带有 AF_ 前缀的输出变量时,它将接管该变量的所有权,这意味着在删除数组时它将被释放。因此,数组指针必须是专门分配并唯一的,因为数组会接管它。 这意味着,在其他方面,您不能使用 std::vector::data 作为指针。

    容器

    • 由于 Python 处理描述符的方式,无法轻松创建类属性。这意味着必须通过 swig 的 cvar 属性访问可变静态类成员,作为类任意成员的属性,或使用 getset 方法。
    • 由于 CppHeaderParser 中的错误,所有成员和继承,即使在结构体中,都必须指定访问权限。

项目详情


下载文件

下载您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源分布

此版本没有可用的源分布文件。请参阅 生成分布存档的教程

构建分布

swimport-3.1.1-py3-none-any.whl (67.4 kB 查看哈希值)

上传时间 Python 3

支持者: