【问题标题】:__getitem__ for multiple attributes of a single class__getitem__ 用于单个类的多个属性
【发布时间】:2016-12-09 21:39:18
【问题描述】:

我有一个 Python 包装的 api,我正在使用 swig 为其生成一个接口,我需要找到一种方法来使用 Python 内置函数 __getitem__ 用于类的不同组件。

一个简单的例子是:

obj = module.class()

for i in range(obj.numPart1()):
    print (obj.part1[i])

for i in range(obj.numPart2()):
    print (obj.part2[i])

如果这是完全错误的,并且有更“Pythonic”的方式来做到这一点,我会全力以赴。基础对象必须有两个部分,因为它是一个三角剖分,可以访问所有与同一个 C++ 后端相关联的顶点和面。


我环顾了很多地方,并尝试在 Python 中使用 metaclassessuper 函数,但运气不佳,主要是因为 SWIG 导致我的类布局方式。我也可能没有完全理解它们......

如果需要,我什至可以将项目添加到 C++ 端,如果我需要能够提供所需的东西,但我想最终得到一个可以访问对象具有的两个属性之一的类本质上是列表。添加到 C++ 端不太理想,因为代码在其他地方重复使用,所以我想尽可能减少对 C++ 代码的干扰。

使用 SWIG 会引起一些我应该提到的问题

  • __init__ 无法修改,因为它是在 SWIG 中生成的。我只能添加到课程中
  • 我输入的任何 Python 代码都必须位于现有类中或位于文件顶部。 (我无法在文件中的任何位置添加类)。

下面是我希望通过具有一定代表性的代码示例访问的样子:

注意:本示例中的所有函数都是包装的 C++ 函数,除了包装的内置函数 (__getitem__,__enter__,__exit__)

import ModTest as m

with m.multiAttrObj() as obj:
   print ("Attribute A:")
   for i in range(obj.nAttrA()):
      #print (obj.getAttrA(i))  # This version works
      print (obj.AttrA[i])      # This version fails

   print ("\nAttribute B:")
   for i in range(obj.nAttrB()):
      #print (obj.getAttrB(i))  # This version works
      print (obj.AttrB[i])      # This version fails

我的模块大概是这样的:

#ModTest.py
class multiAttrObj():
   # Init method cannot be changed...generated by SWIG (in real case)
   def __init__(self):
      # Real module attributes are actually an instance of a C++ object
      self._attrA = "a b c d e f".split()
      self._attrB = "1 2 3 4 5".split()

   def __enter__(self):
      self.__init__
      return self

   def __exit__(self, a,b,c):
      self._attrA = None
      self._attrB = None

   def nAttrA(self):
      return len(self._attrA)

   def nAttrB(self):
      return len(self._attrB)

   # Only way to get to AttrA from Python, calls C++ accessor
   def getAttrA(self,i):
      # real return is as below:
      # return _multiAttrObj.get_attr_a(i)
      return self._attrA[i]  # Example for testing without .pyd

   # Only way to get to AttrB from Python, calls C++ accessor
   def getAttrB(self,i):
      # real return is as below:
      # return _multiAttrObj.get_attr_b(i)
      return self._attrB[i]  # Example for testing without .pyd

   # Function can be created, but throws 
   # TypeError: 'method' object is not subscriptable
   # when called like __getitem__
   #def AttrA(self,i):
   #   return self._attrA[i]

   #def AttrB(self,i):
   #   return self._attrB[i]

   def __getitem__(self,i):
      # This can't distiguish which attribute I want.
      pass

编辑: 回答

从 martineau 修改解决方案,我使用了提供的 Sequence 代理,并为了 SWIG 坚持使用属性,因为我无法重新映射 __getattr____setattr__。在下面回答sn-p:

def _getAttrAWrap(self):
    return SequenceProxy(self.getAttrA, self.nAttrA)

def _getAttrBWrap(self):
    return SequenceProxy(self.getAttrB, self.nAttrB)

# Missing setter functions are for future use.
AttrA = property(_getAttrAWrap, "")
AttrB = property(_getAttrBWrap, "")

【问题讨论】:

  • 欢迎来到Stack Overflow!您可以发布相关异常的实际堆栈跟踪吗?
  • 我很确定你可以(并且应该)让 SWIG 为你处理这个问题。
  • @TemporalWolf:什么例外?本题不涉及任何异常。
  • @TemporalWolf:啊,它被埋在了代码cmets中。
  • 你不能让 SWIG 公开将 _attrA_attrB 公开为 attrAattrB 吗?与__getitem__ 混在一起是错误的做法。

标签: python c++ python-3.x swig


【解决方案1】:

Edit #2.1(稍微调整 Edit #2 之后)

解决这个问题的关键是意识到需要一种方法来覆盖类的属性的索引(通过__getitem__()方法) ,而不是最外面的容器类本身。

要做到这一点,这里有一个更精简的方法,因为它不再使用属性,因此可能是我原始答案的第一个修订版(又名 Edit #1)的更快版本。代替它们,它实现了一个__getattr__() 方法,当属性AttrAAttrB 被引用但还不存在时,该方法通常只会被调用一次。从那时起,它将不再被调用。这解决了无法更改 SWIG 生成的类 __init__() 方法的问题(但不限制该类用作上下文管理器(尽管仍支持用作一个)。

这两个属性在创建时将是一个名为SequenceProxy 的新具体子类的实例,该子类派生自collections.Sequence 抽象基类。这个类类似于常规的list,除了它的自定义__getitem__() 方法调用了一个在创建时提供给它的构造函数的函数。同样,将调用不同的提供函数来确定并在请求时返回其长度。

使用SequenceProxy 实例可以将任何索引和长度查询调用转发到您选择的任何函数或绑定方法,从而提供实现它所需的“钩子”或“瓶颈”。

import collections

class SequenceProxy(collections.Sequence):
    """Proxy class to make something appear to be an (immutable) sized iterable
       container based on just the two functions (or bound methods) provided to
       the constructor.
    """
    def __init__(self, item_getter, length_getter):
        self._item_getter = item_getter
        self._get_length = length_getter

    def __getitem__(self, index):
        return self._item_getter(index)

    def __len__(self):
        return self._get_length()

class multiAttrObj():
    # Init method cannot be changed...generated by SWIG (in real case)
    def __init__(self):
        self._attrA = "a b c d e f".split()
        self._attrB = "1 2 3 4 5".split()

    def __getattr__(self, name):
        """This will create AttrA or AttrB when they are first referenced."""
        if name == 'AttrA':
            self.AttrA = SequenceProxy(self.getAttrA, self.nAttrA)
            return self.AttrA
        elif name == 'AttrB':
            self.AttrB = SequenceProxy(self.getAttrB, self.nAttrB)
            return self.AttrB
        else:
            raise AttributeError('{!r} is not an attribute of {}'.format(
                    name, self.__class__.__name__))

    def __enter__(self):
        return self

    def __exit__(self, *args):
        pass # will implicitly return None which mean handle exceptions normally

    def nAttrA(self):
        return len(self._attrA)  # do whatever is needed here...

    def nAttrB(self):
        return len(self._attrB)  # do whatever is needed here...

    def getAttrA(self, i):
        return self._attrA[i]  # do whatever is needed here...

    def getAttrB(self, i):
        return self._attrB[i]  # do whatever is needed here...

【讨论】:

  • 我删除了一些命名,因为我构建它与原始代码匹配,然后快速查找/替换以删除原始命名。实际代码遵循 PEP8。我将检查这是否适用于实际来源。我需要它来使用 getAttrA 方法,因为它实际上是从 C++ 函数中获取值。我认为这可能是正确的方法是用属性掩盖它。
  • bloominonion:我明白了。有相应的更新答案。
  • 这看起来在经过一些测试后会起作用。一旦我与明天工作的实际来源确认后,我会将其标记为已回答。我也喜欢这样做,因为它可以重复用于我遇到类似问题的任何其他类。感谢您的帮助!
  • bloominonion:我已经发布了我的答案的第二次更新,它不再需要使用属性,并且在其他方​​面更好。看看你是否同意。
  • bloominonion:做了一些相对较小的附加更改(编辑#2.1)。
猜你喜欢
  • 1970-01-01
  • 2013-03-03
  • 2016-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多