Python 的切片到底切了什么:拆开 slice 对象、负步长和 __getitem__

简介: 切片语法糖拆开只有三件事:编译期 BUILD_SLICE 造 slice 对象、BINARY_SUBSCR 交给下标协议、由接收方用 indices() 把边界翻译成具体索引。本文用本机 CPython 3.13.14 实测多维切片传 tuple of slice、负数与超界夹取、list 浅拷贝与 str[:] 复用、memoryview 零拷贝、步长不为 1 的赋值长度约束,并给出自定义视图类与七条排错清单

一句话结论:a[1:5:2] 里的 1:5:2 不是语法里的一段特殊记号,Python 会先在栈上造出一个 slice 对象,再把它整个交给 __getitem__。所以「切片怎么工作」这个问题,本质是「一个对象收到 slice 之后做了什么」。

本文所有输出均来自本机 CPython 3.13.14 实测,代码可直接粘贴运行。

一、先看 __getitem__ 到底收到了什么

写个只负责打印参数的类,把各种切片语法都喂给它:

class Probe:
    def __getitem__(self, k):
        print('  __getitem__ got', type(k).__name__, repr(k))
        return k

p = Probe()
p[1:5:2]
p[:: -1]
p[:]
p[1:3, 2:4]

本机输出:

  __getitem__ got slice slice(1, 5, 2)
  __getitem__ got slice slice(None, None, -1)
  __getitem__ got slice slice(None, None, None)
  __getitem__ got tuple (slice(1, 3, None), slice(2, 4, None))

四个结论一次给全:

  1. 写全的切片 → slice(1, 5, 2);
  2. 省略的位置是 None,不是 0 也不是 len;
  3. a[:] 也会造 slice(None, None, None),并不是「什么都没传」;
  4. 多维切片(NumPy 那种写法)传进去的是一个 tuple,里面每个元素是一个 slice——语言层面根本没有「多维」概念,只是把多个对象打包成元组。

反汇编能验证这确实是两步:

import dis

def f_slice(a): return a[1:5:2]
print([i.opname for i in dis.get_instructions(f_slice)
       if 'SLICE' in i.opname or 'SUBSCR' in i.opname])
# ['BUILD_SLICE', 'BINARY_SUBSCR']

BUILD_SLICE 造对象,BINARY_SUBSCR 调用下标协议。就这么简单。

二、indices() 是负步长与越界的翻译器

slice 自己只存三个属性,不关心序列多长。真正把「负数、超界、负步长」翻译成「可遍历的 start/stop/step」的是 slice.indices(length):

s = slice(1, 5, 2)
print(s.start, s.stop, s.step)        # 1 5 2
print(s.indices(6))                   # (1, 5, 2)
print(slice(None, None, -1).indices(5))   # (4, -1, -1)
print(slice(-100, 100, 2).indices(5))     # (0, 5, 2)

注意第二行:::-1 作用在长度 5 的序列上,被翻译成 (4, -1, -1),stop 是 -1 而不是 0——因为按 -1 的步长走,只有走到 -1 才能包含索引 0。这是手写反转循环最容易错的地方。第三行则演示越界自动夹到合法区间。

对应到真实序列:

a = list(range(10))
print(a[1:5:2])     # [1, 3]
print(a[::-1])      # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
print(a[8:2:-2])    # [8, 6, 4]

只要你要自己实现 __getitem__ 支持切片,就该用 k.indices(len(self.data)),不要自己算负数——上面这些边界你自己写几乎必漏。

三、切片返回的是拷贝还是视图

这是面试里最容易答糊的一问,实测一遍就清楚:

类型 切片结果 是否新对象 备注
list list 是(浅拷贝) 嵌套元素仍共享
str str 通常是 但 st[:] is st → True,解释器会复用整串
bytes bytes 是 不可变
tuple tuple 是(可能复用) 不可变
range range 是 仍是惰性的 range 对象
memoryview memoryview 是(但共享缓冲区) 零拷贝
st = 'abcdefg'
print(st[:] is st)                 # True:整串切片直接复用原对象

nested = [[1], [2], [3]]
shallow = nested[:]
shallow[0].append(99)
print(nested[0])                   # [1, 99] ← 改副本里的子列表,原列表跟着变

mv = memoryview(b'abcdef')
print(bytes(mv[2:5]), mv[2:5].obj is mv.obj)   # b'cde' True

两处要划重点:

  • 列表切片是浅拷贝,不是深拷贝。nested[:] 只复制了外层的引用,里面三个子列表还是原来那三个。想彻底隔离得用 copy.deepcopy。
  • str 的 s[:] 可能就是 s 本身,因为字符串不可变,复用没有副作用。所以别写 if s[:] is not s 之类的判断,它不成立。
  • 真正需要「不复制数据」的场景用 memoryview,它的切片共享底层 buffer,改 view 会影响原缓冲区。

四、赋值和删除切片是另外两个协议

下标语法不只是读。a[1:3] = ... 走 __setitem__,del a[1:4] 走 __delitem__,两者收到的同样是 slice 对象:

class Probe:
    def __setitem__(self, k, v):
        print('  __setitem__ got', type(k).__name__, repr(k), 'value', v)
    def __delitem__(self, k):
        print('  __delitem__ got', type(k).__name__, repr(k))

列表的赋值切片有个反直觉特性:步长 1 的切片赋值可以改变列表长度。

x = list(range(6))
x[1:3] = [90, 91, 92]
print(x)                 # [0, 90, 91, 92, 3, 4, 5]  ← 从 6 个变 7 个

y = list(range(6))
y[::2] = [7, 8, 9]
print(y)                 # [7, 1, 8, 3, 9, 5]

z = list(range(6))
z[::2] = [1, 2]          # ValueError
# attempt to assign sequence of size 2 to extended slice of size 3

规则:步长不等于 1 的「扩展切片」赋值,右侧长度必须与切片选中的位置数严格相等,因为没法靠挪动其它元素来腾位置。这个 ValueError 是排查批量替换逻辑时的高频报错。

islice 则是给任意迭代器做惰性切片的版本,不会先把整个迭代器吃掉:

import itertools
print(list(itertools.islice(range(10), 2, 8, 2)))   # [2, 4, 6]

五、自己写一个「视图」类

把上面几块拼起来,一个支持完整切片语义(含负步长、越界夹取)的最小实现长这样:

class View:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, k):
        if isinstance(k, slice):
            start, stop, step = k.indices(len(self.data))
            return [self.data[i] for i in range(start, stop, step)]
        if isinstance(k, tuple):                 # 多维切片
            return [self.__getitem__(one) for one in k]
        return self.data[k]

    def __len__(self):
        return len(self.data)

v = View(list(range(10)))
print(v[7:1:-2])     # [7, 5, 3]
print(v[1:3])        # [1, 2]
print(v[3])          # 3

要点只有一句:isinstance(k, slice) + k.indices(len(...))。NumPy 的多维索引、Pandas 的 .iloc,往下拆都是这套协议,区别只在于收到 slice 之后是返回拷贝还是视图。

六、六条排错清单

  1. a[::-1] 反转后遍历不到索引 0? 检查你手写的 stop 是不是写了 0;正确翻译是 indices() 给出的 -1。
  2. 改了切出来的列表,原列表也变了? 浅拷贝,嵌套对象共享;要 copy.deepcopy。
  3. s[:] is s 返回 True? 字符串整串切片允许复用原对象,这是实现优化,不是 bug。
  4. a[::2] = [...] 报 ValueError? 扩展切片赋值要求长度严格相等,先 len(a[::2]) 对一下。
  5. memoryview 切片改了原数据? 本来就共享 buffer,零拷贝是它的设计目的。
  6. 自定义类切片不支持负数? 别自己算 k.start % len,改用 k.indices(len(self))。
  7. 给迭代器切片报 TypeError? 迭代器没有 __getitem__,换 itertools.islice。

小结

切片语法糖拆开只有三件事:编译期 BUILD_SLICE 造对象 → BINARY_SUBSCR 交给 __getitem__(或 __setitem__ / __delitem__)→ 由接收方用 indices() 把边界翻译成具体的索引序列。

「返回拷贝还是视图」「长度能不能变」「支不支持多维」,全都不是语言规定的,而是那个对象自己决定的。理解这一点之后,再去看 list、str、memoryview、NumPy 的差异,就不再是一堆要背的特例了。

相关文章
|
13天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)
5606 42
|
14天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
4845 58
|
8天前
|
Linux 开发工具 iOS开发
2026最新codex下载安装保姆级使用教程(下载+安装+登录+使用,含所有历史版本)
本文详解OpenAI Codex国内快速上手指南:提供Win/macOS/Linux全平台免安装包、npm/Homebrew等多方式安装、账号/API Key双登录、简体中文切换,覆盖下载、安装、汉化、使用及高频避坑(如微软商店失败、npm超时等),10分钟即可跑通。
2179 6
|
6天前
|
人工智能 安全 API
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(dsh)是DeepSeek AI于2026年开源的轻量级Agent运行框架,基于Cordis内核,践行“一切皆插件”理念。支持Web/桌面双端,开箱即用;内置多模型接入、丰富插件生态(超3.5万),适配编程、办公、创作等场景。(239字)
1345 0
|
14天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
2300 7
|
9天前
|
云安全 人工智能 安全
|
1天前
|
云安全 人工智能 安全
|
7天前
|
人工智能 并行计算 调度
本地 AI 一键部署工具 LocModel:Windows 端集成画图、视频生成、Agent 的开源模型运行方案
LocModel 是专为 Windows 设计的一站式本地 AI 工具,支持零配置启动、自动硬件检测、GGUF 模型一键加载、文生图/视频生成及 Agent 自动化任务。适配 8G 显存,内置显存分片调度,轻松实现 AI 漫剧批量创作与离线大模型调用。(239 字)