jjzjj

python - SWIG 将 C 库连接到 Python(从 C 'iterable' 结构创建 'sequence' Python 数据类型)

coder 2023-08-22 原文

我已经为 C 库编写了一个 Python 扩展。我有一个看起来像这样的数据结构:

typedef struct _mystruct{
   double * clientdata;
   size_t   len;
} MyStruct;

此数据类型的用途直接映射到 Python 中的列表数据类型。因此,我想为导出的结构创建“类似列表”的行为,以便使用我的 C 扩展编写的代码更“Pythonic”。

特别是,这是我希望能够做的(来自 python 代码) 注意:py_ctsruct 是在 python 中访问的 ctsruct 数据类型。

我的需求可以概括为:

  1. list(py_ctsruct) 返回一个 python 列表,其中包含从 c 结构中复制的所有内容
  2. py_cstruct[i] 返回 ith<​​ m=""> 元素(最好在无效索引上抛出 IndexError)
  3. 对于 py_ctsruct 中的元素:枚举的能力

根据 PEP234 , 一个对象可以用“for”迭代,如果它实现 _iter_() 或 _getitem_()。然后使用该逻辑,我认为通过将以下属性(通过 rename )添加到我的 SWIG 接口(interface)文件中,我将获得所需的行为(除了上面的要求。#1 - 我仍然不知道如何实现):

__len__
__getitem__
__setitem__

我现在可以在 python 中索引 C 对象。我还没有实现 Python 异常抛出,但是如果超出数组边界,将返回一个魔数(Magic Number)(错误代码)。

有趣的是,当我尝试使用“for x in”语法遍历结构时:

for i in py_cstruct:
    print i

Python 进入一个无限循环,它只是在控制台上打印上面提到的魔数(Magic Number)(错误)。这向我表明索引有问题。

最后但并非最不重要的一点是,我该如何实现要求 1?这涉及(据我了解):

  • 处理'来自python的函数调用list()
  • 从 C 代码返回 Python(列表)数据类型

[[更新]]

我有兴趣查看有关我需要在接口(interface)文件中放入哪些(如果有的话)声明的小代码片段,以便我可以从 Python 迭代 c 结构的元素。

最佳答案

最简单的解决方案是实现 __getitem__并抛出 IndexError无效索引的异常。

我举了一个例子,在 SWIG 中使用 %extend%exception 来实现 __getitem__ 并分别引发异常:

%module test

%include "exception.i"

%{
#include <assert.h>
#include "test.h"
static int myErr = 0; // flag to save error state
%}

%exception MyStruct::__getitem__ {
  assert(!myErr);
  $action
  if (myErr) {
    myErr = 0; // clear flag for next time
    // You could also check the value in $result, but it's a PyObject here
    SWIG_exception(SWIG_IndexError, "Index out of bounds");
  }
}

%include "test.h"

%extend MyStruct {
  double __getitem__(size_t i) {
    if (i >= $self->len) {
      myErr = 1;
      return 0;
    }
    return $self->clientdata[i];
  }
}

我通过添加到 test.h 来测试它:

static MyStruct *test() {
  static MyStruct inst = {0,0};
  if (!inst.clientdata) {
    inst.len = 10;
    inst.clientdata = malloc(sizeof(double)*inst.len);
    for (size_t i = 0; i < inst.len; ++i) {
      inst.clientdata[i] = i;
    }
  }
  return &inst;
}

并运行以下 Python:

import test

for i in test.test():
  print i

打印:

python run.py
0.0
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0

然后结束。


另一种方法,使用类型映射将 MyStruct 直接映射到 PyList 也是可能的:

%module test

%{
#include "test.h"
%}

%typemap(out) (MyStruct *) {
  PyObject *list = PyList_New($1->len);
  for (size_t i = 0; i < $1->len; ++i) {
    PyList_SetItem(list, i, PyFloat_FromDouble($1->clientdata[i]));
  }

  $result = list;
}

%include "test.h"

这将创建一个 PyList,其中包含任何返回 MyStruct * 的函数的返回值。我测试了这个 %typemap(out),其功能与之前的方法完全相同。

你也可以写一个相应的 %typemap(in)%typemap(freearg) 作为反向,像这样未经测试的代码:

%typemap(in) (MyStruct *) {
  if (!PyList_Check($input)) {
    SWIG_exception(SWIG_TypeError, "Expecting a PyList");
    return NULL;
  }
  MyStruct *tmp = malloc(sizeof(MyStruct));
  tmp->len = PyList_Size($input);
  tmp->clientdata = malloc(sizeof(double) * tmp->len);
  for (size_t i = 0; i < tmp->len; ++i) {
    tmp->clientdata[i] = PyFloat_AsDouble(PyList_GetItem($input, i));
    if (PyErr_Occured()) {
      free(tmp->clientdata);
      free(tmp);
      SWIG_exception(SWIG_TypeError, "Expecting a double");
      return NULL;
    }
  }
  $1 = tmp;
}

%typemap(freearg) (MyStruct *) {
  free($1->clientdata);
  free($1);
}

使用迭代器对于像链表这样的容器更有意义,但为了完整起见,您可能会如何使用 __iter__MyStruct 执行此操作。关键是让 SWIG 为您包装另一种类型,它提供了所需的 __iter__()next(),在本例中为 MyStructIter 是使用 %inline 同时定义和包装的,因为它不是普通 C API 的一部分:

%module test

%include "exception.i"

%{
#include <assert.h>
#include "test.h"
static int myErr = 0;
%}

%exception MyStructIter::next {
  assert(!myErr);
  $action
  if (myErr) {
    myErr = 0; // clear flag for next time
    PyErr_SetString(PyExc_StopIteration, "End of iterator");
    return NULL;
  }
}

%inline %{
  struct MyStructIter {
    double *ptr;
    size_t len;
  };
%}

%include "test.h"

%extend MyStructIter {
  struct MyStructIter *__iter__() {
    return $self;
  }

  double next() {
    if ($self->len--) {
      return *$self->ptr++;
    }
    myErr = 1;
    return 0;
  }
}

%extend MyStruct {
  struct MyStructIter __iter__() {
    struct MyStructIter ret = { $self->clientdata, $self->len };
    return ret;
  }
}

iteration over containers 的要求容器需要实现 __iter__() 并返回一个新的迭代器,但除了返回下一个项目并递增迭代器本身的 next() 之外还必须提供一个 __iter__() 方法。这意味着可以相同地使用容器或迭代器。

MyStructIter 需要跟踪迭代的当前状态 - 我们在哪里以及我们还剩下多少。在这个例子中,我通过保留一个指向下一个项目的指针和一个我们用来告诉我们何时结束的计数器来做到这一点。您还可以通过保持指向迭代器正在使用的 MyStruct 的指针和其中位置的计数器来跟踪状态,例如:

%inline %{
  struct MyStructIter {
    MyStruct *list;
    size_t pos;
  };
%}

%include "test.h"

%extend MyStructIter {
  struct MyStructIter *__iter__() {
    return $self;
  }

  double next() {
    if ($self->pos < $self->list->len) {
      return $self->list->clientdata[$self->pos++];
    }
    myErr = 1;
    return 0;
  }
}

%extend MyStruct {
  struct MyStructIter __iter__() {
    struct MyStructIter ret = { $self, 0 };
    return ret;
  }
}

(在这种情况下,我们实际上可以只使用容器本身作为迭代器作为迭代器,通过提供返回容器的副本__iter__()和一个类似于第一种类型的 next()。我在原来的答案中没有这样做,因为我认为这比有两种不同的类型更不清晰——容器和该容器的迭代器)

关于python - SWIG 将 C 库连接到 Python(从 C 'iterable' 结构创建 'sequence' Python 数据类型),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8776328/

有关python - SWIG 将 C 库连接到 Python(从 C 'iterable' 结构创建 'sequence' Python 数据类型)的更多相关文章

  1. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  4. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  5. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  6. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  7. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

    我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

  8. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  9. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  10. ruby - 如何使用 RSpec::Core::RakeTask 创建 RSpec Rake 任务? - 2

    如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake

随机推荐