__getitem__ 单个 class 的多个属性

__getitem__ for multiple attributes of a single class

我有一个 Python 包装的 api,我正在使用 swig 为其生成接口,我需要找到一种方法来使用 Python class 的不同组件的内置函数 __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++ 后端的顶点和面。


我环顾了很多地方,我尝试使用 metaclasses 和 Python 中的 super 函数,但没有成功,主要是因为我的classes 是因为 SWIG 而布局的。我也可能没有完全理解它们......

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

使用 SWIG 会导致一些我应该提到的问题


下面是我希望使用具有代表性的代码示例进行访问的样子:

注意:此示例中的所有函数都是包装的 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 的解决方案,我使用了提供的序列代理,并且为了 SWIG 而坚持使用属性,因为我无法重新映射 __getattr____setattr__。下面的答案片段:

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, "")

编辑 #2.1(稍微调整编辑 #2 后)

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

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

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

使用 SequenceProxy 实例提供了将任何索引和长度查询调用转发到您选择的任何函数或绑定方法的机会——从而提供 "hooks" 或 "bottlenecks" 所需的实现它。

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...