你几乎必须做这样的事情......但至少你可以删除一些重复。
首先,考虑[1,]
表示“第 1 行”可能是合理的,就像[1]
. (numpy
这样做。)这意味着您不需要 tuple-vs.-int 的东西;只需将 int 视为 1 元素元组。换句话说:
def __getitem__(self, idx):
if isinstance(idx, numbers.Integral):
idx = (idx, slice(None, None, None))
# now the rest of your code only needs to handle tuples
其次,尽管您的示例代码只处理两个切片的情况,但您的实际代码必须处理两个切片,或者一个切片和一个 int,或者一个 int 和一个切片,或者两个 int,或者一个切片,或者一个 int。如果您可以分解出切片处理代码,则无需一遍又一遍地复制它。
处理 int-vs.-slice 的一个技巧是将[n]
其视为一个包装器,本质上[n:n+1][0]
,它可以让您进一步减少所有内容。(这比这有点棘手,因为您必须对一般的负数进行特殊处理,或者只是-1
,因为很明显n[-1] != n[-1:0][0]
。)对于一维数组,这可能不值得,但对于二维数组,它可能是,因为这意味着当你处理列时,你总是得到一个行列表,而不仅仅是一行。
另一方面,您可能希望在__getitem__
和之间共享一些代码__setitem__
……这使得这些技巧中的一些要么不可能实现,要么变得更加困难。所以,有一个权衡。
无论如何,这是一个示例,它完成了我能想到的所有简化和预处理/后处理(可能比您想要的更多),因此最终您总是在查找一对切片:
class Matrix(object):
def __init__(self):
self.m = [[row + col/10. for col in range(4)] for row in range(4)]
def __getitem__(self, idx):
if isinstance(idx, (numbers.Integral, slice)):
idx = (idx, slice(None, None, None))
elif len(idx) == 1:
idx = (idx[0], slice(None, None, None))
rowidx, colidx = idx
rowslice, colslice = True, True
if isinstance(rowidx, numbers.Integral):
rowidx, rowslice = slice(rowidx, rowidx+1), False
if isinstance(colidx, numbers.Integral):
colidx, colslice = slice(colidx, colidx+1), False
ret = self.m[rowidx][colidx]
if not colslice:
ret = [row[0] for row in ret]
if not rowslice:
ret = ret[0]
return ret
或者,如果您沿另一个轴重构事物可能会更好:获取行,然后获取其中/它们中的列:
def _getrow(self, idx):
return self.m[idx]
def __getitem__(self, idx):
if isinstance(idx, (numbers.Integral, slice)):
return self._getrow(idx)
rowidx, colidx = idx
if isinstance(rowidx, numbers.Integral):
return self._getrow(rowidx)[colidx]
else:
return [row[colidx] for row in self._getrow(rowidx)]
这看起来要简单得多,但我在这里通过将第二个索引转发到 normal 来作弊list
,这只是因为我的底层存储是 a list
of list
s。但是,如果您有任何类型的可索引行对象要遵循(并且它不会浪费不可接受的时间/空间来不必要地创建这些对象),您可以使用相同的作弊方法。
如果您反对在 index 参数上进行类型切换,是的,这似乎通常是 unpythonic,但不幸的是它__getitem__
通常是如何工作的。如果你想使用通常的 EAFTPtry
逻辑,你可以,但是当你必须在多个地方尝试两个不同的 API(例如,[0]
用于元组和.start
切片)时,我认为它不会更具可读性。您最终会在顶部进行“鸭子类型切换”,如下所示:
try:
idx[0]
except AttributeError:
idx = (idx, slice(None, None, None))
……等等,这只是没有任何通常好处的普通类型切换的代码的两倍。