Python:将 SwigPythonObject 转换为 Python 对象

mic*_*ael 1 python swig

我正在使用一些封闭的 Python 模块:我可以通过 API 调用方法,但无法访问实现。我知道这个模块基本上包含了一些 C++ 代码。

因此方法返回值类型之一是 a SwigPythonObject。假设我没有来自模块分发者或文档的任何其他帮助,我以后如何使用该对象?

我想以某种方式将他转换为“常规”python 对象,并在调试器中观察他的内部成员结构。

目前我在调试器中看到的是这样的:

{SwigPythonObject} _<hexa number>_p_unsigned_char
Run Code Online (Sandbox Code Playgroud)

Fle*_*exo 5

您所要求的语义有点不清楚,但基本上看起来好像您有一个指向unsigned char您想要使用的 SWIG 的指针。稍微猜测一下,您可能会遇到以下 3 种情况:

  1. 该指针实际上是指向单个无符号字节的指针
  2. 该指针是指向空终止字符串的指针。(为什么不把它直接包裹成一根绳子呢?)
  3. 该指针指向一个固定长度的无符号字节数组。(你需要以某种方式知道/猜测长度)

在这个特定的实例中,因为所有三种情况都不需要担心打包或对齐,我们实际上可以为上述所有情况编写一些东西,使用 ctypes 来读取 SWIG 直接引用到 Python 中的内存,并绕过 SWIG 代理。(请注意,如果我们正在查看的类型不仅仅是指向单个内置类型或它们的数组的指针,那么我们在这里将无法做太多事情)

首先在 C - test.h 中编写一些代码来练习我们正在做的事情:

inline unsigned char *test_str() {
  static unsigned char data[] = "HELLO WORLD";
  return data;
}

inline unsigned char *test_byte() {
  static unsigned char val = 66;
  return &val;
}
Run Code Online (Sandbox Code Playgroud)

接下来是一个最小的 SWIG 模块,其中包含以下内容:

%module test

%{
#include "test.h"
%}

%include "test.h"
Run Code Online (Sandbox Code Playgroud)

我们可以在 ipython 中检查这一点,并看到它与您观察到的内容(类似)进行了包装:

In [1]: import test

In [2]: test.test_byte()
Out[2]: <Swig Object of type 'unsigned char *' at 0x7fc2851cbde0>

In [3]: test.test_str()
Out[3]: <Swig Object of type 'unsigned char *' at 0x7fc2851cbe70>

In [4]: hex(int(test.test_str()))
Out[4]: '0x7f905b0e72cd'
Run Code Online (Sandbox Code Playgroud)

我们在每种情况下使用的事实是,调用int(x)where x 是我们未知的 SWIG unsigned char 指针,为我们提供指针所指向的地址的值作为整数。结合 ctype 的from_address静态方法,我们可以构造 ctypes 实例来直接访问 SWIG 知道的内存。(注意:调用返回的地址int()与字符串表示形式显示的地址不匹配,因为前者是指向数据的真实地址,但后者是 SWIG代理对象的地址)

也许最简单的包装是固定长度的情况 - 我们可以使用正确大小的*运算符创建一个 ctypes 类型,然后调用.c_ubytefrom_address

对于以 null 结尾的字符串情况,我们实际上有两个选择:要么使用 libc strlen 函数计算字符串长度,然后构造一个匹配的 ctypes 类型,要么只是从 Python 中逐个字符循环,直到遇到 null 。我在下面的示例中选择了后者,因为它更简单。itertools.count()我可能通过使用生成器并跟踪位置而使其变得过于复杂。

最后,对于指向单字节情况的指针,我基本上重用了现有的 ctypes 类型,我必须创建一个 1 字节数组并从中读取值。可能有一种方法可以使用ctypes.POINTER(ctypes.c_ubyte)then从地址构造类型.contents,但我无法快速看到它,因此使用 1 字节数组技巧对我来说变得微不足道。

所有这些结合起来给出了以下 Python 代码:

import ctypes
import test
import itertools

# Case 2
def swig_to_str(s):
  base = int(s)
  ty = ctypes.c_ubyte*1
  def impl():
    for x in itertools.count():
      v=ty.from_address(base+x)[0]
      if not v: return
      yield chr(v)
  return ''.join(impl())

# Case 1
def swig_to_byte(b):
  ty=ctypes.c_ubyte*1
  v=ty.from_address(int(b))
  return v[0]

# Case 3
def swig_to_fixed_len(s, l):
  ty=ctypes.c_ubyte*l
  return ''.join(chr(x) for x in ty.from_address(int(s)))

t=test.test_str()
print(t)
print(swig_to_str(t))
print(swig_to_fixed_len(t,5))

u=test.test_byte()
print(u)
print(swig_to_byte(u))
Run Code Online (Sandbox Code Playgroud)

这在 Python 2.7 中按预期运行(应该花费最少的努力使其正确用于 3):

import ctypes
import test
import itertools

# Case 2
def swig_to_str(s):
  base = int(s)
  ty = ctypes.c_ubyte*1
  def impl():
    for x in itertools.count():
      v=ty.from_address(base+x)[0]
      if not v: return
      yield chr(v)
  return ''.join(impl())

# Case 1
def swig_to_byte(b):
  ty=ctypes.c_ubyte*1
  v=ty.from_address(int(b))
  return v[0]

# Case 3
def swig_to_fixed_len(s, l):
  ty=ctypes.c_ubyte*l
  return ''.join(chr(x) for x in ty.from_address(int(s)))

t=test.test_str()
print(t)
print(swig_to_str(t))
print(swig_to_fixed_len(t,5))

u=test.test_byte()
print(u)
print(swig_to_byte(u))
Run Code Online (Sandbox Code Playgroud)