Swig/Python: 对象不支持索引

7

给定这组文件:

foo.h:

#pragma once

#include <stdio.h>

template <class T0> class Foo {
  public:
    T0 m[3];

    Foo(const T0 &a, const T0 &b, const T0 &c) {
        m[0] = a;
        m[1] = b;
        m[2] = c;
    }
    void info() { printf("%d %d %d\n", m[0], m[1], m[2]); }
    // T0 &operator[](int id) { return ((T0 *)m)[id]; }
};

foo.cpp:

#include "foo.h"

foo.i(尝试1):

%module foo

%{
#include "foo.h"
%}

%include "foo.h"

%template(intFoo) Foo<int>;

%extend Foo{
    T0& __getitem__(int id) { return ((T0 *)m)[id]; }
}

setup.py:

import os
import sys
from setuptools import setup, Extension

foo_module = Extension('_foo',
                           sources=[
                               'foo.i',
                               'foo.cpp'
                           ],
                           swig_opts=['-c++', '-py3', '-builtin'],
                           include_dirs=['.']
                           )

setup(name='foo',
      version='0.1',
      platforms=['Windows', 'Linux'],
      ext_modules=[foo_module],
      py_modules=["foo"],
      )

test.py:

from foo import intFoo

a = intFoo(10,20,30)
print(dir(a))
a.info()
print(a[2])

我构建了运行的扩展程序:

python setup.py build_ext --force -i

但是当我尝试运行 test.py 文件时,会出现以下错误:
TypeError: 'foo.intFoo' object does not support indexing

foo.i中的extend语句是其他SO相关线程上建议的答案,这意味着我在这里使用它不正确。有人能解释一下如何修复此问题,以便当我运行test.py时可以成功使用[]运算符吗?

其他尝试:

  • Attempt2:

    %module foo
    
    %{
    #include "foo.h"
    %}
    
    %include "foo.h"
    
    %template(intFoo) Foo<int>;
    
    %extend intFoo{
        T0& __getitem__(int id) { return ((T0 *)m)[id]; }
    }
    

    Throws this error TypeError: 'foo.intFoo' object does not support indexing

  • Attempt3

    %module foo
    
    %{
    #include "foo.h"
    %}
    
    %include "foo.h"
    
    %extend Foo{
        T0& __getitem__(int id) { return ((T0 *)m)[id]; }
    }
    
    %template(intFoo) Foo<int>;
    

    Throws this error foo_wrap.cpp(3808): error C2065: 'm': undeclared identifier


尝试使用 %extend intFoo 或将 %extend Foo 移至 %template(intFoo) 之前。 - Mark Tolonen
很高兴看到一个带有setup.py和所有必要文件的问题,以便有效地重现您的问题。 - Flexo
这是一个重复的问题,链接为https://dev59.com/VX7aa4cB1Zd3GeqPminO。由于有未完成的悬赏,此问题无法被关闭。 - R Sahu
@Flexo,我对Swig不太熟悉。我相信你的判断。 - R Sahu
@RSahu 几天前我已经看了你提到的问题和其他问题,并尝试过,当然它们没有帮助,否则我就不会打开这个问题了。 - BPL
显示剩余4条评论
2个回答

7
<在本示例中,我正在使用您的foo.i文件的第一个版本。> <首先,您的%extend需要在%template指令之前指定才能生效。> <一旦我们修复了这个问题,现在你的%extend代码会导致编译错误: >
foo_wrap.cpp: In function 'int& Foo_Sl_int_Sg____getitem__(Foo<int>*, int)':
foo_wrap.cpp:3705:85: error: 'm' was not declared in this scope

这是因为使用 % extend 添加的方法实际上不是您添加它们的类的成员。在此上下文中访问 m,我们需要改用 $self->m 进行引用。 SWIG 将为我们替换 $self 适当的变量。 (值得快速查看生成的代码以了解其工作原理)。
调试类型映射或扩展的一个有用提示是在 SWIG 生成的输出中搜索您编写的代码-如果不存在,则说明它的应用方式与您想象的不同。
所以一旦我们修复有关未声明 m 的错误,我们又遇到了另一个问题,因为您已经使用 -builtin 编译:
In [1]: import foo

In [2]: f=foo.intFoo(1,2,3)

In [3]: f[0]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-e71eec16918d> in <module>()
----> 1 f[0]

TypeError: 'intFoo' object does not support indexing

In [4]: f.__getitem__(0)
Out[4]: <Swig Object of type 'int *' at 0xa8807d40>

即使您已经添加了__getitem__方法,使用f[n]进行索引仍然不起作用。这是因为在纯C-API类中,Python的运算符重载方式与普通类不同。您已经成功添加了__getitem__方法,但Python正在查找内置类型插槽(具体来说是mp_subscript)以执行该操作。因此,我们也需要修复它。完成后,一个可用的foo.i如下所示:
%module foo

%{
#include "foo.h"
%}

%feature("python:slot", "mp_subscript", functype="binaryfunc") Foo::__getitem__;

%include "foo.h"

%extend Foo{
    T0& __getitem__(int id) { return ((T0 *)$self->m)[id]; }
}

%template(intFoo) Foo<int>;

所以现在我们可以做你想要的事情:
In [1]: import foo

In [2]: f=foo.intFoo(1,2,3)

In [3]: f[0]
Out[3]: <Swig Object of type 'int *' at 0xb4024100>

实际上,您不需要再称其为__getitem__,因为该函数已在插槽中注册,例如可以完全不使用%extend来调用operator[].

最后,您可能希望将返回类型更改为const T0&,或编写Python类型以更好地代理非const int引用对象。


它的功能非常好,但是如果在 T0& 前面没有加上 const,返回类型就会变成一个 Swig Object of type 'int *'。加上 const 后,它就变成了一个整数。 - Jens Munk

3

我已经更正了错误的答案(声明%extent只能在没有-builtin选项的情况下使用),但是仅适用于没有“内置”选项的情况下。

setup.py

import os
import sys
from setuptools import setup, Extension

foo_module = Extension('_foo',
                           sources=[
                               'foo.i', 'foo.cpp'
                           ],
                           swig_opts=['-c++'],
                           include_dirs=['.']
                           )

setup(name='foo',
      version='0.1',
      platforms=['Windows', 'Linux'],
      ext_modules=[foo_module],
      py_modules=["foo"],
      )

foo.i

%module foo

%{
#include "foo.h"
%}

%include "carrays.i"
%array_functions(int, intArray);

%include "foo.h"

%extend Foo<int> {
%pythoncode %{
def __getitem__(self, id):
  return _foo.intArray_getitem(self.m,id)             
%}
};

%template(intFoo) Foo<int>;

请注意,这个扩展正在生成Python代码,这允许您做许多复杂的事情。
原回答:
根据SWIG文档,Python层被剥离,因此忽略了%extend功能(这是不正确的,代理对象没有被创建)。
请参见http://www.swig.org/Doc3.0/SWIGDocumentation.html

36.4.2 内置类型

当使用-builtin时,纯python层被剥离。每个包装类都变成一个新的Python内置类型,继承自SwigPyObject,并直接从包装方法中返回SwigPyObject实例。有关Python内置扩展的更多信息,请参阅Python文档:


"%extend" 应用于 C++ 层,而不是 Python 层,所以这里不是答案。实际的答案在 36.4.2.2 中。 - Flexo
@Flexo 我有几个使用%extend的例子,唯一的效果是在Python层注入代码。对我来说,去除built-in标志很好用,当然也可以使用$self引用对象。 - Jens Munk
你能分享到pastebin或其他地方吗?%extend是通用的SWIG,不是Python模块中的内容,所以我不明白怎么会出现这种情况。%pythoncode%pythonprepend%feature('shadow')都是Python特定的,不能在-builtin中使用,但是%extend就像你在C或C++声明/定义中编写的一样。 - Flexo
我写了 %extend Foo<int> { %pythoncode %{ def __getitem__(int i): ...。这样,扩展只会影响 Python 代码。我从未在没有 %pythoncode 的情况下使用过 %extend。我每天都在学习。 - Jens Munk
哈哈,我从未在%extend中使用过%pythoncode,而是通过使用模块级别的%pythoncode进行“猴子补丁”来添加更多的纯Python方法。 - Flexo
我喜欢你的方法——越多翻译成C语言越好。 - Jens Munk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接