我希望获得创建类似Python内置类型的数据结构的经验。作为第一个练习,我编写了一个WraparoundList类,它意味着与内置的list完全相同,除了访问超出范围的元素“包装”之外。
目标:
list不同的行为是当使用[]显式索引时。collections模块中看起来不会太不合适。下面是包含doctest的完整源代码:
#!/usr/bin/env python
from sys import maxint as MAXINT
class WraparoundList(list):
"""A list whose index wraps around when out of bounds.
A `WraparoundList` is the same as an ordinary `list`,
except that out-of-bounds indexing causes the index
value to wrap around. The wrapping behavior is as if
after reaching the last element, one returned to the
other end of the list and continued counting.
>>> x = WraparoundList('abcd')
>>> x
['a', 'b', 'c', 'd']
>>> x[3]
'd'
>>> x[4] # wraps to x[0]
'a'
>>> x[-6] = 'Q' # wraps to x[-2]
>>> x
['a', 'b', 'Q', 'd']
>>> del x[7] # wraps to x[3]
>>> x
['a', 'b', 'Q']
Indices used in out-of-range slices also wrap around.
If the slice's `start` or `stop` is out-of-bounds, it
gets wrapped around.
>>> x = WraparoundList('abcd')
>>> x
['a', 'b', 'c', 'd']
>>> x[:10] # wraps to x[:2]
['a', 'b']
>>> x[-7:3] # wraps to x[-3:3]
['b', 'c']
The one way in which slicing a `WraparoundList` differs
from slicing an ordinary `list` is the case of using the
list length as the upper limit.
>>> x = WraparoundList('abcd')
>>> x
['a', 'b', 'c', 'd']
>>> x[2:]
['c', 'd']
>>> x[2:4] # wraps to x[2:0]
[]
Initializing a `WraparoundList` with a nested iterable
does not cause inner indices to wrap. To have a multi-
dimensional `WraparoundList`, all the elements of the
outer `WraparoundList` must also be `WraparoundList`s.
>>> x = WraparoundList([list('abc'), list('def')])
>>> x
[['a', 'b', 'c'], ['d', 'e', 'f']]
>>> x[3]
['d', 'e', 'f']
>>> x[3][5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> y = WraparoundList([WraparoundList(i) for i in x])
>>> y[3][5]
'f'
"""
def __getitem__(self, i):
"""x.__getitem__(i) <=> x[i]"""
if isinstance(i, slice):
return list.__getitem__(self, self._wrap_slice(i))
else:
return list.__getitem__(self, self._wrap_index(i))
def __getslice__(self, i, j):
"""x.__getslice__(i, j) <=> x[i:j]"""
return self.__getitem__(slice(i, j, None))
def __setitem__(self, i, y):
"""x.__setitem__(i, y) <=> x[i] = y"""
if isinstance(i, slice):
list.__setitem__(self, self._wrap_slice(i), y)
else:
list.__setitem__(self, self._wrap_index(i), y)
def __setslice__(self, i, j):
"""x.__setslice__(i, j) <=> x[i:j] = y"""
self.__setitem__(slice(i, j, None))
def __delitem__(self, i):
"""x.__delitem__(i, y) <=> del x[i]"""
if isinstance(i, slice):
list.__delitem__(self, self._wrap_slice(i))
else:
list.__delitem__(self, self._wrap_index(i))
def __delslice__(self, i, j):
"""x.__delslice__(i, j) <=> del x[i:j]"""
self.__delitem__(slice(i, j, None))
def _wrap_index(self, i):
_len = len(self)
if i >= _len:
return i % _len
elif i < -_len:
return i % (-_len)
else:
return i
def _wrap_slice(self, slc):
if slc.start is None:
start = None
else:
start = self._wrap_index(slc.start)
if slc.stop is None:
stop = None
elif slc.stop == MAXINT:
# __*slice__ methods treat absent upper bounds as sys.maxint, which would
# wrap around to a system-dependent (and probably unexpected) value. Setting
# to `None` in this case forces the slice to run to the end of the list.
stop = None
else:
stop = self._wrap_index(slc.stop)
step = slc.step
return slice(start, stop, step)
def main():
pass
if __name__ == '__main__':
main()发布于 2017-01-29 18:48:59
WraparoundList切片不同于普通list切片的一种方法是使用列表长度作为上限。但这并不是全部的事情--普通的list也可以使用大于列表长度的值进行切片,在这种情况下,WraparoundList也有不同的行为:>>> x= 1,2,3 >>> x*10 >>>x= WraparoundList(x) >>> x*10sys.maxint (Python3中的所有整数都是“长”的)。我建议这样做:在Python2.7中尝试:#,当调用__*slice__方法时没有“停止”#值,则传递sys.maxint。从sys导入maxint作为NO_STOP,除了ImportError:#Python3没有sys.maxint或使用__*slice__方法。NO_STOP = object()我更喜欢像NO_STOP这样的名称,因为它传递的是意图,而不是实现。_wrap_index将引发ZeroDivisionError:>>> w= WraparoundList([]) >>> w0回溯(最近一次调用):文件"",第1行,在文件“cr153920.py”中,第79行,在__getitem__返回list.__getitem__(self,self._wrap_index(i))文件"cr153920.py",第110行,在_wrap_index返回I% _len ZeroDivisionError:整数除法或以零引发异常是正确的。但我希望能得到一个IndexError。list.__getitem__,而不是通过super函数调用。但是,如果某个人的另一个类C也继承了list并重写了__getitem__方法,并通过继承组合了WraparoundList和C,这就有一个令人不满意的结果:类D(WraparoundList,C):pass然后D()[0]调用WraparoundList.__getitem__,后者调用list.__getitem__,但是C.__getitem__从未被调用,这与人们所期望的相反。如果您想支持WraparoundList的子类,那么您需要编写:返回超级(WraparoundList,self).__getitem__(self._wrap_slice(i))等等。_wrap_slice只从一个地方调用,因此它可以在其使用点被内联。main函数或一个if __name__ == '__main__':部分--如果没有什么可做的,那么就没有必要编写代码来完成它。发布于 2017-12-15 00:03:15
除了Gareth的出色回答:由于WraparoundList和通常的list的行为不同(例如,当使用大于列表长度的值进行切片时),您的类不尊重Liskov代换原理 (参见较不正式的解释)。
简单地说,因为一些使用list的代码在使用WraparoundList时会表现不同,所以即使尊重"WraparoundList是一个列表“关系,WraparoundList也不应该从list继承。
改变这种情况的一种方法可能是停止从list继承,而是在内部使用list存储数据(组合重于继承)。
https://codereview.stackexchange.com/questions/153920
复制相似问题