Python SWIG对象比较

5

我有两个SWIG对象列表:a和b。我需要进行集合或比较操作,以查找在a中但不在b中的项目。(我还有其他操作要做,但这是一个很好的起点示例)。

set(a) -set(b) 

不给出准确的结果, 所以我尝试了:

[item for item in a if item not in b]

在这两种情况下,它都不返回任何项,即使a和b没有共同的元素。
我在a中有一个值为的项:
<Swig Object of type 'OpenAccess_4::oaRect *' at 0x1ad6eea0>

并且 b 中的一个项目:

<Swig Object of type 'OpenAccess_4::oaRect *' at 0x1ad6eca8>

当我进行比较时,考虑到的是==。

'is'运算符可以正常工作,但如果要对两个列表进行单独比较,则非常耗时,因为它们可能很大,并且该操作会重复多次。

我在Python中使用SWIG对象时错过了什么,不允许我进行'=='和'set'操作?


1
你能贴一些代码吗,这样我们就可以重现问题了吗? - dwitvliet
问题在于这些对象是从一个大型的开放访问数据库中提取出来的。我正在寻找一些知识片段,或许可以解释为什么<SWIG Object a>不能与<SWIG Object b>进行比较。这是因为包装对象有它们自己的运算符吗? - Paul Nelson
你所谈论的根本问题不需要任何数据库层面的东西来复现。制作最小可能的完整示例,让任何人都能够复现它,这样你就更有可能得到答案。 - Flexo
1个回答

14

只要您的对象实现了Python对象协议,那么您所关心的高级容器操作就会自动工作。为了展示它们是如何被实现的,我们将重点放在operator==/__eq__上。

我们可以建立一个测试案例来研究Python和SWIG中比较是如何工作的:

import test
f1 = test.foo(1)
f2 = test.foo(2)
f3 = test.foo(1)
f4 = test.static_foo()
f5 = test.static_foo()
a = (f1,f2,f3,f4,f5)

compared = "\n".join((",".join(str(int(y==x)) for y in a) for x in a))

print compared

print "\n".join((str(x) for x in a))

以我们天真的实现为起点:

%module test

%inline %{
struct foo {
  foo(const int v) : v(v) {}
  int v;
};

foo *static_foo() {
  static foo f{1};
  return &f;
}
%}

当运行此代码时,会出现以下结果:
1,0,0,0,0
0,1,0,0,0
0,0,1,0,0
0,0,0,1,0
0,0,0,0,1
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb71e79f8> >
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb71e7ad0> >
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb71e7b78> >
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb71e7b90> >
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb71e7b60> >

这几乎不是我们所希望的,它只是单位矩阵。

那么为什么会出现这种情况呢?首先,SWIG默认为从C++返回到Python的所有内容构造一个新的代理对象。即使在静态情况下,SWIG的输出在编译时也无法证明始终返回相同的对象,因此为了安全起见,它总是创建一个新的代理。

在运行时,我们可以添加类型映射来检查和处理该情况(例如使用std::map进行实例查找)。但这是一个单独的问题,而且会分散注意力,因为它不会使f1==f3,因为它们是不同但等效的对象。

请注意,在C++中我们也有同样的问题,但由于各种设计原因,我们甚至无法编译使用operator==的简单函数:

bool bar() {
  static foo f1{2};
  static foo f2{2};
  return f1==f2;
}

编译失败,错误信息如下:

test.cxx:6 error: no match for ‘operator==’ in ‘f1 == f2’

让我们来探索一下,了解SWIG在生成Python包装器时的行为。如果我们给C++类添加一个operator==

%module test

%inline %{
struct foo {
  foo(const int v) : v(v) {}
  int v;
  bool operator==(const foo& o) const { return v == o.v; }
};

foo *static_foo() {
  static foo f{1};
  return &f;
}

突然之间 SWIG 做了正确的事情,并将其传递给 Python,因此我们的测试用例现在会生成:

1,0,1,1,1
0,1,0,0,0
1,0,1,1,1
1,0,1,1,1
1,0,1,1,1
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb72869f8> >
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb7286ad0> >
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb7286a70> >
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb7286bc0> >
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb7286bd8> >

这正是我们所希望的行为,所有相同的v实例都是相等的,而另一个则不同。这是尽管代理对象在每个实例中都是不同的。

如果我们编写的operator==是非成员运算符会发生什么?

%module test

%inline %{
struct foo {
  foo(const int v) : v(v) {}
  int v;
};

foo *static_foo() {
  static foo f{1};
  return &f;
}

bool operator==(const foo& a, const foo& b) { return a.v== b.v; }

突然间我们失去了预期的行为,现在我们又回到了原点。

1,0,0,0,0
0,1,0,0,0
0,0,1,0,0
0,0,0,1,0
0,0,0,0,1

为什么?因为在这种情况下,如何处理operator==并不是很清楚。如果你使用-Wall运行SWIG,你会看到我们现在收到了一个警告:

test.i:15: Warning 503: Can't wrap 'operator ==' unless renamed to a valid identifier.

假设我们无法编辑C++代码,那么就需要考虑“修复”问题的几种方法。

  1. We could ask SWIG to rename the operator== that it doesn't know how to wrap to be a function using %rename as hinted by the warning:

    %rename(compare_foo) operator==(const foo&, const foo&);
    

    This needs to be written anywhere before the declaration/definition of our operator== is seen by SWIG.

    In and of itself this is not sufficient to restore the behaviour we wanted though, so we fix it by adding some extra Python to the output of SWIG. Recall that the Python function __eq__ has the following form:

    object.__eq__(self, other)
    

    That's actually a pretty good match for our C++ operator== still, so we can simply add the following at the end of the SWIG interface file:

    %pythoncode %{
      foo.__eq__ = lambda a,b: compare_foo(a,b)
    %}
    

    Which restores the behaviour we're after. (Note: I'm not sure why the lambda is needed here, I wasn't expecting it to be required)

  2. We could also do this by writing some more C++ in our interface, but not having to modify the actual code we're wrapping. Basically what we want to do is implement __eq__ inside foo. This can be done with %extend which extends a class, but only from the perspective of the target language. For completeness we use %ignore to suppress the function that we're getting a warning about since we have dealt with the problem.

    %module test
    
    %ignore operator==(const foo&, const foo&);
    %extend foo {
      bool __eq__(const foo& o) const {
        return *$self == o;
      }
    }
    
    %inline %{
    struct foo {
      foo(const int v) : v(v) {}
      int v;
    };
    
    foo *static_foo() {
      static foo f{1};
      return &f;
    }
    
    bool operator==(const foo& a, const foo& b) { return a.v== b.v; }
    
    bool bar() {
      static foo f1{2};
      static foo f2{2};
      return f1==f2;
    }
    %}
    

    Which again restores the behaviour we're after at, with the difference being that the glue is included as C++ glue rather than Python glue.

  3. Finally if you're running SWIG Python with -builtin neither of those solutions will work for the non-member operator case. It's worth noting that the default SWIG output includes a tp_richcompare function. Still to use our operator== instead of an address comparison of the underlying objects you'll need to use the slots mechanism to register our own function similar to the above code.


这很好,解释了为什么 SWIG 对象不能正确比较。 - Paul Nelson
Flexo,我们这里不习惯使用C++来进行工作。我们基本上是通过使用Open Access DB软件包继承了SWIG对象。有没有一种方法可以在Python内部完成这个任务?不仅仅是使用“==”,而是使用“set”运算符,使用“in”来确定列表或字典成员资格等。 - Paul Nelson
@paul 这有点取决于 swig 包创建时的选项。如果没有使用 -builtin,那么你可以在纯 Python 中实现比较函数。 - Flexo
有没有简单的方法可以告诉呢? - Paul Nelson
def foo(self): pass,然后 module.typename.bar = foo。如果这样可以运行,那你就可以继续了。 - Flexo
嗯,我认为这是在说:如果你可以为类定义一个新属性,那么显然你能够直接访问它。但是,当使用模块 'oa' 和对象 oaPath 时,我尝试执行以下代码:>>>oa.oaPath.bar=3 Traceback (innermost last): File "<stdin>", line 1, in <module> AttributeError: type 'oaPath' has no attribute 'bar' 我还尝试在上面定义你的方法 'foo',但是得到了相同的消息。我将尝试一个解决方法。非常感谢。 - Paul Nelson

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