__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 会导致一些我应该提到的问题
__init__
无法修改,因为它是在 SWIG 中生成的。我只能添加到class
- 我输入的任何 Python 代码必须在现有 class 中或文件顶部。 (我无法在文件中的任何位置添加 classes)。
下面是我希望使用具有代表性的代码示例进行访问的样子:
注意:此示例中的所有函数都是包装的 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__()
方法,而不是它们,当属性 AttrA
或 AttrB
被引用但尚不存在时,该方法通常只会被调用一次。从那时起,它不会再被调用。这解决了无法更改 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...
我有一个 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 会导致一些我应该提到的问题
__init__
无法修改,因为它是在 SWIG 中生成的。我只能添加到class- 我输入的任何 Python 代码必须在现有 class 中或文件顶部。 (我无法在文件中的任何位置添加 classes)。
下面是我希望使用具有代表性的代码示例进行访问的样子:
注意:此示例中的所有函数都是包装的 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__()
方法,而不是它们,当属性 AttrA
或 AttrB
被引用但尚不存在时,该方法通常只会被调用一次。从那时起,它不会再被调用。这解决了无法更改 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...