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_python
和append_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.Trigger
、swimport.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默认使用以下规则来分类方法参数。这些可以通过更改单个MethodBehaviour
的parameter_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
映射导入所有原始类型(包括bool
、size_t
和wchar_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::string
和std::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++类型ellipsis
、NoneType
、NotImplemented
和PySingleton
。
切片
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.list
、pools.set
、pools.frozenset
和pools.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 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ea228ae0abbf43dc2bfa27747a370ecdc9b488da224a04b7c7aeed2c698452ed |
|
MD5 | aa303fe97b3e21396b630ac45b5781ce |
|
BLAKE2b-256 | ca228a15404cbedd95a5f9b45bdab648bff86338f14bfb77d0eef8b253792f8f |