# 切片的实现

# list的切片实现

在 Python 中,列表对象的切片是通过内置的 slice()​ 函数实现的。slice()​ 函数的调用方式如下所示:

slice(start, stop[, step])

l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
print(l[::-1])
# 等价于
s = slice(None, None, -1)
print(l[s])

其中,start​ 表示切片开始的位置,stop​ 表示切片结束的位置,step​ 表示切片的步长。如果 start​ 和 step​ 都没有给出,则默认为 0​ 和 1​。

当进行切片操作时,Python 会创建一个新的列表对象,然后从原来的列表对象中复制出一段数据,然后将其赋值给新的列表对象。具体来说,切片的实现过程如下:

  1. 首先,根据 start​、stop​ 和 step​ 参数计算出切片的起始位置、终止位置和步长。
  2. 然后,创建一个新的列表对象,用来存储切片后的数据。
  3. 接着,根据起始位置、终止位置和步长,从原来的列表对象中复制出一段数据。
  4. 最后,将复制出来的数据赋值给新的列表对象。

需要注意的是,切片操作只是复制了原列表的一部分元素,而并没有改变原列表本身。因此,如果对新列表进行修改操作,不会影响原列表。

这个复制是很有趣的,因为python中一切皆对象,所以实际上复制过去的是指向对象的指针,所以对象可变则实际上还是会同步改变,对象不可变就会看着像不同步改变。

但是!仔细考虑,即使对象可变,可变的是其内部状态,其本身是没有任何变化的,即id(object)是没有变化的,所以说其不同步改变是没有问题的。

l = [[1],[2],[3]]
sl = l[::2]
print(sl)           # [[1], [3]]
for p in sl:
    p.append(0)
print(sl)           # [[1, 0], [3, 0]]
print(l)            # [[1, 0], [2], [3, 0]]
sl[1] = "10086"
print(sl)           # [[1, 0], '10086']
print(l)            # [[1, 0], [2], [3, 0]]
l = list(range(20))
l_reversed = l[::-1]
l_reversed[0] = -1
print(l)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
print(l_reversed)  # [-1, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

# 多维切片和...

实际上python的切片操作时支持多维切片的,当进行多维切片时,即使用类似l[1:2,2:3,3:4]​的语法时,实际上__getitem__​接收到的index是一个slice的元组。

同时注意Python在切片中存在...​的写法,其看上去虽然是省略号(注意实际写法是三个英文句号,而不是输入法打出来的省略号),但实际上是eclipse​类的唯一实例Ellipsis​的别名,其是切片规范的一部分,用户需要自己处理Ellipsis-Python内建数据类型都只支持一维切片,因此没有Ellipsis​用武之地。

l[1:2,2:3,3:4]      # {'index': (slice(1, 2, None), slice(2, 3, None), slice(3, 4, None))}
l[1:2,...,3:4]      # {'index': (slice(1, 2, None), Ellipsis, slice(3, 4, None))}
l[1:2,...,3:4,...]  # {'index': (slice(1, 2, None), Ellipsis, slice(3, 4, None), Ellipsis)}

l[1:...]            # {'index': slice(1, Ellipsis, None)}

# 自定义数据结构实现切片

# 切片操作
class MyList(object):

    def __init__(self):
        self.mlist = list()

    def __getitem__(self, index):
        print('__getitem__() called  ', locals())
        if isinstance(index, slice):
            return self.mlist[index]

    def __setitem__(self, index, value):
        print('__getitem__() called  ', locals())
        if isinstance(index, slice):
            self.mlist[index] = value

    def __delitem__(self, index):
        print('__delitem__() called  ', locals())
        if isinstance(index, slice):
            del self.mlist[index]

l = MyList()

l[0]                # {'index': 0}
l[::-1]             # {'index': slice(None, None, -1)}
l[0:10:1]           # {'index': slice(0, 10, 1)}

l[1:2,2:3,3:4]      # {'index': (slice(1, 2, None), slice(2, 3, None), slice(3, 4, None))}
l[1:2,...,3:4]      # {'index': (slice(1, 2, None), Ellipsis, slice(3, 4, None))}
l[1:2,...,3:4,...]  # {'index': (slice(1, 2, None), Ellipsis, slice(3, 4, None), Ellipsis)}

l[1:...]            # {'index': slice(1, Ellipsis, None)}