第3章:Introducing Lists

建立列表索引、增删改、返回值、排序副作用和越界错误的可观察模型。

学习目标

  • 能解释list的有序、可变语义,计算zero-based index与negative index对应的元素
  • 能实现append、insert、pop、remove等list mutation,并区分副作用和return value
  • 能比较sort、sorted、reverse与len的契约,分析并复现IndexError边界

为什么列表不是“很多变量放在一起”

list是一个有顺序、可变、可容纳任意对象引用的container。名字guests绑定到list对象,list内部每个槽位再引用具体元素;因此修改某个索引是改变容器中的引用,而重新绑定guests是让名字指向另一个list。这个双层结构会在下一章解释copy和slice。

What Is a List的核心不只是方括号语法,而是三个契约:顺序决定索引,长度决定有效边界,可变性允许原地改变。先预测["Ada", "Linus", "Grace", "Guido"]的索引0-14分别发生什么,再操作图示。

Accessing Elements:正负索引映射到同一边界

分步1 / 3

选择正索引或负索引

languages = ["Python", "Rust", "Go", "C++"]
print(languages[0])   # Python
print(languages[-1])  # C++
print(languages[-2].upper())
 
last_index = len(languages) - 1
print(languages[last_index])

negative index从尾部读取:-1是最后一个,-len(items)是第一个。它适合表达“最后一次记录”,但空列表没有任何有效正负索引。若数据可能为空,先通过领域规则处理empty case,而不是默认items[-1]一定安全。

IndexError 是边界证据,不是随机故障

访问items[len(items)]必然越界,因为有效位置到len(items)-1结束。IndexError指出调用方对长度的假设失效:可能数据为空、循环上界多走一步、删除后仍使用旧索引,或外部输入没有验证。修复应更新错误假设,而不是简单把索引减一。

Modifying, Adding, and Removing Elements

list mutation直接改变原list,因此所有指向同一list的名字都会观察到结果。append在尾部添加,insert在指定位置前插入,索引赋值替换一个槽位;del按位置删除但不返回元素,pop删除并返回元素,remove按值删除第一个匹配项。

分步1 / 3

切换四种列表变更操作

tasks = ["design", "test"]
tasks.append("deploy")
tasks.insert(1, "implement")
 
done = tasks.pop(0)
tasks.remove("test")
print(done)   # design
print(tasks)  # ['implement', 'deploy']

按位置删除还是按值删除

如果协议说“完成队尾任务”,使用pop()并消费return value;如果说“删除第一个名为test的任务”,使用remove("test");如果只知道位置且不需要对象,del tasks[index]最明确。先写出业务意图再选method,能避免重复值下删除错对象。

不要在迭代同一list时随意删除元素。删除会让后续元素左移,循环可能跳过候选;下一章将学习通过slice复制、comprehension或构建新列表表达筛选。当前章节先把一次mutation的before、after与returned value都验证清楚。

用列表身份解释多个名字看到的变化

执行primary = ["A", "B"]后,名字primary指向一个list;再执行alias = primary不会复制元素,只让alias指向同一list。于是alias.append("C")后,primary也打印三个元素。变化发生在共同对象上,不是Python把一条append命令神秘地广播给所有变量。可以用primary is alias验证身份相同,但业务代码更应通过明确ownership避免依赖隐式alias。

重新绑定与原地修改必须分开推理。alias = ["X"]只让alias改指向新list,primary仍保留旧list;alias[:] = ["X"]则修改alias当前指向的list内容,若它仍与primary共享对象,primary会观察到替换。两行外观相近,却一个改变name binding,一个改变container state,因此调试时要记录对象身份和before/after,而不是只读变量名。

函数接收list时也会获得对同一对象的引用。若函数调用append,调用者可观察副作用;这有时是契约,例如把完成任务加入共享队列,但若函数名只是normalize_names,隐式修改输入会令人意外。第8章会正式设计函数边界,现在先养成命名:原地操作使用明确动词并在文档中说明,纯转换返回new list。

当list包含可变元素时,外层与内层还有两层身份。即使下一章用slice复制外层list,内部dictionary或nested list仍可能共享。当前章节至少要能画出“name → outer list → element object”三段引用,并指出一次mutation作用在哪一层。这个模型让排序、删除和嵌套数据错误都能回到具体owner,而不是归因于语言不可预测。

测试mutation时,before snapshot不能写成before = items,否则before与after共享对象,断言失去意义。对本章的一层简单值可用before = items.copy();遇到嵌套可变结构时再评估是否需要deepcopy或重新设计数据边界。证据的前提是快照本身不随被测操作一起变化。

Organizing a List:区分新视图与原地改变

sorted(items)返回新的有序list,原list保持不变;items.sort()原地排序并返回None。reverse()只颠倒当前顺序,不等于降序排序;若要降序结果,可使用sorted(items, reverse=True)items.sort(reverse=True)len(items)只读取元素数量,不改变容器。

分步1 / 3

选择排序或反转操作

destinations = ["Tokyo", "Reykjavik", "Lima", "Oslo"]
 
ordered = sorted(destinations)
print(destinations)  # original order
print(ordered)       # alphabetical view
 
destinations.sort(reverse=True)
print(destinations)  # original list mutated
print(len(destinations))

排序需要显式比较规则

字符串默认按Unicode码点比较,大小写和重音符号会影响结果。若产品要求大小写不敏感,可使用key=str.casefold;若对象是字典或instance,就用key提取稳定字段。排序前要决定重复键是否保留输入顺序、缺失字段如何处理,以及是否允许原地改变共享list。

Python排序是stable sort:key相同的元素保持原相对顺序。这允许先按次要字段排序,再按主要字段排序,或直接提供tuple key。但初学阶段优先写一个清晰key函数,打印输入和输出验证规则,不要把复杂lambda与mutation混在同一行。

从单个元素到完整状态变更记录

列表操作最容易出现“结果看起来对,但原输入已被破坏”。一个可复查的练习应保存before snapshot、执行操作、记录return value、检查after state,并覆盖空列表、重复值、首尾索引。这样下一章进入loop和slice时,能清楚知道每一步读的是哪份状态。

在项目代码里还要区分用户可见顺序与内部处理顺序。若UI中的收藏列表必须保持添加顺序,临时展示时用sorted view;若列表本身就是优先队列的简化实现,原地sort也许符合契约。技术API没有替你决定领域含义,函数名和测试必须补上这一层。

本章回顾:每次操作都回答四个问题

  1. list是ordered mutable list,index由顺序与长度定义。
  2. zero-based index和negative index最终都映射到0..len-1,越界产生IndexError。
  3. append/insert/pop/remove的副作用和return value不同,按业务意图选择。
  4. sorted返回新list,sort/reverse是destructive methods,len只读长度。
  5. 变更前后都记录state、return value和错误边界,才能解释alias观察到的结果。

练习

问题 1:给定四个元素,哪些索引有效:034-1-4-5

问题 2:实现任务状态迁移:尾部加入deploy,取出第一个任务作为done,再删除第一个test。

问题 3:如何得到大小写不敏感的有序展示,同时保留原始输入顺序?

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

ordered mutable list
zero-based index
index boundary
list mutation
destructive method

资料与写作方式声明

本章以Python Crash Course, Third Edition, Chapter 3: Introducing Lists权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…