【问题标题】:Specify a Default Offset for Python List指定 Python 列表的默认偏移量
【发布时间】:2021-01-31 19:02:13
【问题描述】:

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

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

这样每当使用索引进行访问/修改时,索引将首先添加偏移量:

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

【问题讨论】:

  • 你的意思是a[n]应该返回a[n] + offset?没有内置方法,但您可以创建自定义函数或类来获取此行为
  • @Anonymous,抱歉不清楚,我的意思是a[n] 会给a[n + offset]
  • 我很好奇:你为什么需要这个?
  • @AKX,我刚解决leetcode问题Edit Distance,发现在处理动态规划时,有时dp列表的索引和字符串的索引不是对齐,因为基本情况的索引是 -1,所以我必须对字符串的索引或 dp 列表的索引应用偏移量,如果手动执行,这有点容易出错。
  • 也许您应该考虑一个不是有效索引器的基本情况的值,例如None

标签: python list offset


【解决方案1】:

没有内置的方法可以实现这一点。但是,您可以通过扩展 list 来创建自定义类以获得此行为。当您执行my_list[n] 时,内部会触发__getitem__() 函数。您可以通过将offset 添加到索引来覆盖此函数以返回值。

同样,列表包含其他magic functions,您可以覆盖这些magic functions,以进一步修改自定义类的行为。比如__setitem__()在你给list赋值的时候触发,__delitem__()在删除item的时候触发。

这是创建OffsetList 类的示例代码,它在创建list 时将附加参数作为offset,并对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__() 等。

【讨论】:

    【解决方案2】:

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

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

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


    请注意,Python确实让您能够定义自己的功能:

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 2014-08-01
        • 2014-09-28
        • 1970-01-01
        • 1970-01-01
        • 2018-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多