为 Python 列表指定默认偏移量

Specify a Default Offset for Python List

python 中有没有一种方法可以为列表指定默认偏移量? 喜欢:

a = [0, 1, 2, 3, 4, 5, 6]
a.offset = 2

这样每当access/modify使用索引时,索引都会先加上偏移量:

a[0] == 2
a[4] == 6

Python 或我所知道的任何其他语言都没有这样的功能。假设您可以获得批准的功能,您建议的语法是合理的。但是,它有几个缺点。

直到并且除非此功能成为普遍使用,否则任何试图阅读此类代码的人都会感到困惑。零基和一基索引是“规则”;任意索引违反了长期以来的假设。

您会严重限制 Python 的右端索引:语义不明确。如果有人写 a[-1] 来访问最后一个元素,他们应该得到那个元素(这是语言定义的习语),原始 a[1] 元素(根据您的定义),“反射” a[-3],或 index out of bounds 试图向右移动两个元素?


请注意,Python 可以让您定义自己的功能:

class

任何时候您不喜欢给定的数据类型,您都可以创建自己的数据类型。您不能更改内置类型,但您可以通过继承 list 并编写自己的 get 和其他方法来做您喜欢的事情。

如果您只是从列表中读取数据,您可能会使用原始数据的下标副本:

a = [0, 1, 2, 3, 4, 5, 6] 
a = a[2:]

a[0] == 2 # True
a[4] == 6 # True

请记住,这会使用相同的变量名创建列表的副本,因此您将丢失原始内容(索引 0 和 1)。如果你确实需要它,你可以将它保存在一个单独的变量中:

a = [0, 1, 2, 3, 4, 5, 6] 
a0,a = a,a[2:]

a[0] == 2 # True
a[4] == 6 # True

a0[0] == 0 # True
a0[4] == 4 # True

如果你真的需要一个具有读写能力的原始数组视图,那么我建议使用 numpy 数组:

import numpy as np

a = np.array([0, 1, 2, 3, 4, 5, 6])
b = a[2:].view()

b[0] == 2  # True
b[4] == 4  # True

b[1] = 99
print(a)   # [ 0  1  2 99  4  5  6]
a[3] == 99 # True 

如果你想自己实现类似于 numpy 的东西,你可以创建一个 class 代表列表上的一个“视图”,内部切片 属性(开始、停止、步骤) :

class ListView:

    def __init__(self,aList,start=None,stop=None,step=1):
        self.data   = aList
        self.slice  = slice(start,stop,step)
        
    @property
    def indices(self): return range(len(self.data))[self.slice]

    def offset(self,index=None):
        if not isinstance(index,slice): return self.indices[index]
        first = self.indices[index][0]  
        last  = self.indices[index][-1] 
        step  = (index.step or 1)*(self.slice.step or 1)
        return slice(first,last+1-2*(step<0),step)         
        
    def __len__(self): return len(self.indices)

    def __getitem__(self,index): return self.data[self.offset(index)]

    def __repr__(self): return self[:].__repr__()

    def __iter__(self): return self[:].__iter__()

    def __setitem__(self,index,value): self.data[self.offset(index)] = value

    def __delitem__(self,index): del self.data[self.offset(index)]

用法:

a = list(range(1,21))

v = ListView(a,3,-2,2)

len(v)  # 8

print(a) 
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

print(v) 
# [4, 6, 8, 10, 12, 14, 16, 18]

v[2] += 80

print(a)
# [1, 2, 3, 4, 5, 6, 7, 88, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

v.slice = slice(-4,None,-3)
print(v)
# [17, 14, 11, 88, 5, 2]

没有实现此目的的内置方法。但是,您可以通过扩展触发 list to get this behaviour. When you do my_list[n], internally __getitem__() 函数来创建自定义 class。您可以通过将 offset 添加到索引来将此函数覆盖为 return 值。

同理,list contains other magic functions which you can override to further modify the behaviour of your custom class. For example, __setitem__() is triggered when you assign any value to list, __delitem__()删除item时触发

这是创建 OffsetList class 的示例代码,它在创建 list 时将附加参数作为 offset,并对 [=40= 执行基于索引的操作]index+offset值.

class OffsetList(list):
    def __init__(self, offset, *args, **kwargs):
        super(OffsetList, self).__init__(*args, **kwargs)
        self.offset = offset

    def _get_offset_index(self, key):
        if isinstance(key, slice):
            key = slice(
              None if key.start is None else key.start + self.offset,
              None if key.stop is None else key.stop + self.offset,
              key.step
            )
        elif isinstance(key, int):
            key += self.offset
        return key

    def __getitem__(self, key):
        key = self._get_offset_index(key)
        return super(OffsetList, self).__getitem__(key)

    def __setitem__(self, key, value):
        key = self._get_offset_index(key)
        return super(OffsetList, self).__setitem__(key, value)

    def __delitem__(self, key):
        key = self._get_offset_index(key)
        return super(OffsetList, self).__delitem__(key)

样本运行:

# With offset as `0`, behaves as normal list
>>> offset_list = OffsetList(0, [10,20,30,40,50,60])
>>> offset_list[0]
10

# With offset as `1`, returns index+1
>>> offset_list = OffsetList(1, [10,20,30,40,50,60])
>>> offset_list[0]
20

# With offset as `2`, returns index+2
>>> offset_list = OffsetList(2, [10,20,30,40,50,60])
>>> offset_list[0]
30

# Slicing support, with `start` as start+offset and `end` as end+offset
>>> offset_list[1:]
[40, 50, 60]

# Assigning new value, based on index+offset
>>> offset_list[0] = 123
>>> offset_list
[10, 20, 123, 40, 50, 60]

# Deleting value based on index+offset
>>> del offset_list[0]
>>> offset_list
[10, 20, 40, 50, 60]

同样,您可以根据需要修改 __len__(), __iter__(), __repr__(), __str__() 等其他魔法函数的行为。