前面五节的数据都是"一个一个的"(一个数、一段文字)。本节开始处理"一串一串的"——一组任务、一句话、一批传感器读数。 两样东西贯穿整节:列表(一串可以改的东西)和字符串(一串不能改的文字)。它们共享同一套"下标与切片"规则,学一次两边通用。
6-3 学过一个字符串 str,当时只用了它的"拼接"和 f-string;本节把它的下标、切片、方法全展开。
6-4 学的 for 循环,当时只能 range() 数数字;本节给它真正的用武之地——遍历一串真实的数据(for task in todos:)。
6-5 学的函数,本节会把处理数据的逻辑包成函数。
读法建议:本节是本章最后一节,也是以后所有硬件实验的地基——传感器读回来的永远是一串数据,不是单个数字。
一、先建立一个概念:序列
Python 里有一类东西叫序列——它是"排好队的一串值",每个值都有一个位置编号(下标)。
本节讲三种:
| 类型 | 写法 | 能改吗 | 什么时候用 |
|---|---|---|---|
列表 list |
[1, 2, 3] |
能改 | 一堆会变的数据(待办事项、传感器读数) |
字符串 str |
"abc" |
不能改 | 一段文字 |
元组 tuple |
(1, 2, 3) |
不能改 | 一组固定的值(坐标、配置) |
三种都是"一排格子,每个格子有编号"。 下标(从第几个)和切片(取连续的一段)的规则,三种完全一样。 唯一的区别是:列表能改内容,字符串和元组不能。
二、下标:从 0 开始数
fruits = ["苹果", "香蕉", "橙子", "葡萄"]
想象成这样一排带编号的格子:
| 下标 | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 值 | 苹果 | 香蕉 | 橙子 | 葡萄 |
| 负下标 | -4 | -3 | -2 | -1 |
>>> fruits[0]
'苹果'
>>> fruits[3]
'葡萄'
>>> fruits[-1] # 负数从右边数:-1 是最后一个
'葡萄'
>>> fruits[-2]
'橙子'
在 C++ 里想拿到最后一个元素要写 arr[arr.size() - 1](还得判断空不空);Python 直接 fruits[-1]。
这个写法你以后会天天用——因为"最新的一条记录""最后读到的那个值"总是最后一个。
越界会报错(Python 不会像 C 那样"悄悄读后面的内存",这是它安全的地方):
>>> fruits[10]
IndexError: list index out of range
三、切片:一次取一串
规则:[起点:终点:步长]。
最重要的一条(和 range 完全一致):含头不含尾。
>>> nums = [10, 20, 30, 40, 50]
>>> nums[1:4] # 下标 1、2、3(不含 4)
[20, 30, 40]
>>> nums[:3] # 省略起点 = 从头开始
[10, 20, 30]
>>> nums[2:] # 省略终点 = 一直到末尾
[30, 40, 50]
>>> nums[:] # 全都要(可以理解成复制一份)
[10, 20, 30, 40, 50]
>>> nums[::2] # 从头到尾,每隔一个取一个
[10, 30, 50]
>>> nums[::-1] # 步长 -1 = 倒着取 → 反转!
[50, 40, 30, 20, 10]
一行把一串东西倒过来,别的语言要写个循环。记住它,以后会经常用到("最新的消息排最前面")。
验证一下为什么是 [::-1]:起点省略(从头)、终点省略(到尾)、步长 -1(每次往左走一步)——走出来的顺序自然就是反的。
切片最实用的用途:提取一段。 比如从日期字符串里取年份:
>>> date = "2026-09-24"
>>> date[:4] # 前四个字符
'2026'
>>> date[5:7] # 第 5、6 个(跳过 '2026-' 的横线?不,看下面)
'09'
>>> date.split("-") # 更好的办法:直接切开
['2026', '09', '24']
2026-09-24 的下标是:2(0) 0(1) 2(2) 6(3) -(4) 0(5) 9(6) -(7) 2(8) 4(9)。
所以 [5:7] 取的是下标 5、6 两个字符 → "09" ✅
但要小心:这种"数格子"的写法很脆(换个格式就崩)。格式规整的数据,优先用 split() 切开(下面讲)。
四、列表:能改的一串
4.1 创建、查看、修改、增删
todos = ["买菜", "洗衣服"] # 创建
todos[0] = "买菜和水果" # 修改(列表能改)
print(todos) # ['买菜和水果', '洗衣服']
todos.append("遛狗") # 末尾追加(最常用)
print(todos) # ['买菜和水果', '洗衣服', '遛狗']
todos.insert(0, "起床") # 插到指定位置
print(todos) # ['起床', '买菜和水果', '洗衣服', '遛狗']
todos.remove("洗衣服") # 按内容删(删掉第一个匹配的)
print(todos) # ['起床', '买菜和水果', '遛狗']
last = todos.pop() # 弹出最后一个(并把它交给你)
print(last) # 遛狗
print(todos) # ['起床', '买菜和水果']
print(len(todos)) # 2 —— 数个数
print(todos.count("起床")) # 1 —— 数某个值出现几次
print("起床" in todos) # True —— 在不在里面
最常用的六个方法(先记这四个就行):
| 方法 | 作用 | 例子 |
|---|---|---|
.append(x) |
末尾追加一个 | todos.append("倒垃圾") |
.remove(x) |
按内容删掉一个 | todos.remove("起床") |
.pop() |
弹出末尾(或指定位置) | last = todos.pop() |
.sort() |
排序(原地改) | nums.sort() |
.reverse() |
反转(原地改) | nums.reverse() |
.clear() |
清空 | todos.clear() |
另外三个"函数"形式(不是方法,写在括号外面):
| 函数 | 作用 | 例子 |
|---|---|---|
len(x) |
有几个元素 | len(todos) |
sorted(x) |
排序(返回新的,不动原列表) | sorted(nums) |
sum(x) |
求和(元素都是数字时) | sum([1, 2, 3]) → 6 |
>>> todos = ["买菜"]
>>> todos = todos.append("洗衣服") # ❌ 想"更新一下"
>>> print(todos)
None # 列表没了!
为什么:.append() 是"原地修改"——它直接改 todos 本身,不返回任何东西(返回 None)。
你把它的返回值(None)赋给了 todos,原来的列表就被覆盖掉了。
正确写法(不加 =):
todos.append("洗衣服") # ✅ 直接调用,不要赋值
规律:"原地改"的列表方法(append / remove / sort / reverse / clear)一律返回 None。
相反,sorted()(函数形式)返回新列表——名字带 ed 的通常是"返回新的",不带的是"原地改的",这个规律很实用。
4.2 遍历列表(for 的真正用武之地)
todos = ["起床", "买菜", "做饭"]
for task in todos:
print(f"○ {task}")
输出:
○ 起床
○ 买菜
○ 做饭
想要序号怎么办?用 enumerate():
for i, task in enumerate(todos, 1): # 第二个参数 1 表示"从 1 开始编号"
print(f"{i}. {task}")
输出:
1. 起床
2. 买菜
3. 做饭
它的意思是"边遍历边报号"——每轮给你两个东西:序号 和 元素本身。
左边写两个变量接住(i, task),就是 6-5 学的解包。
默认序号从 0 开始,加个 1 就变成从 1 开始——这才是给人看的编号。
4.3 .sort() 的一个小脾气
nums = [30, 5, 100, 20]
nums.sort()
print(nums) # [5, 20, 30, 100] ✅ 数字会按大小排
words = ["banana", "Apple", "cherry"]
words.sort()
print(words) # ['Apple', 'banana', 'cherry'] ← 大写排前面
字符串排序是按"字符编码"排的,所以大写字母全部排在小写字母前面(A=65,a=97)。别以为它坏了——想忽略大小写写 words.sort(key=str.lower)。
倒序:nums.sort(reverse=True)。
4.4 元组:不能改的列表
point = (3, 5) # 元组用小括号
x, y = point # 解包
print(x, y) # 3 5
point[0] = 10 # ❌ TypeError: 'tuple' object does not support item assignment
什么时候用元组:当这组值本来就不该被改(坐标、RGB 颜色、一条数据库记录)。用元组是一种"声明":告诉读代码的人"这是固定的"。
五、字符串:不能改的一串字符
5.1 字符串也有下标和切片(规则完全一样)
>>> s = "Raspberry"
>>> s[0]
'R'
>>> s[-1]
'y'
>>> s[:9]
'Raspberry'
>>> s[::-1]
'yrettopsaR' # 反转
5.2 字符串不能改(这是它和列表的根本区别)
>>> s = "hello"
>>> s[0] = "H"
TypeError: 'str' object does not support item assignment
想改怎么办?造一个新的:
>>> s = "hello"
>>> s = "H" + s[1:] # 拼一个新的,赋回给 s
>>> s
'Hello'
因为字符串用得太多、改得太少。让"不能改"的东西存在,Python 才能放心地把它共享给多处使用(省内存、也更安全)。 记住这条规矩:字符串的所有"修改类"方法,都是返回一个新字符串,原字符串不动。
>>> s = " hello "
>>> s.strip() # 去掉两头空格
'hello'
>>> s # 原字符串没变!
' hello '
想生效就必须接住返回值:s = s.strip()。这一条和上一节那个 .append() 的坑刚好相反——一个是"必须接",一个是"千万别接",特别容易搞混。
5.3 七个最常用的字符串方法
s = " Hello, Raspberry Pi "
s.strip() # 'Hello, Raspberry Pi' ← 去掉两头空白(最常用!)
s.strip().lower() # 'hello, raspberry pi' ← 全变小写
s.strip().upper() # 'HELLO, RASPBERRY PI' ← 全变大写
s.strip().replace("Pi", "树莓派") # 'Hello, Raspberry 树莓派' ← 替换(全部替换)
s.strip().find("Rasp") # 7 ← 找子串的位置,找不到返回 -1
s.strip().startswith("He") # True
s.strip().split(",") # ['Hello', ' Raspberry Pi'] ← 按分隔符切成列表
用户在输入框里手一滑多加了个空格,是最常见的数据污染:
answer = input("继续吗?(y/n) ").strip().lower()
if answer == "y":
...
这一行里塞了两个"清洗"动作:去掉两头空白、统一成小写。这样用户敲 Y、y、y、Y 都能通过。
养成习惯:input() 拿到的东西,用之前先 strip() 一下。
5.4 split() 和 join():字符串与列表之间的双向门
>>> line = "苹果,香蕉,橙子"
>>> parts = line.split(",") # 字符串 → 列表
>>> parts
['苹果', '香蕉', '橙子']
>>> "-".join(parts) # 列表 → 字符串
'苹果-香蕉-橙子'
>>> " ".join(["你好", "世界"]) # 用空格拼
'你好 世界'
是"分隔符".join(列表),不是"列表".join(分隔符)。 一开始都觉得别扭(凭什么分隔符写前面),但记住它的固定形态就行:
"分隔符".join(列表名)
这个写法在硬件项目里超常用——把一串传感器读数拼成一行日志就靠它。
5.5 好消息:Python 处理中文不用怕
这一点是 Python 相对 C++ 的一大优势,值得单独说。
在 字符数组 里踩过一个坑:UTF-8 下一个汉字占 3 个字节,用 substr 按字节切会把汉字切成两半、输出乱码。
在 Python 里完全没有这个问题:
>>> s = "你好树莓派"
>>> len(s)
5 # ← 是 5 个"字",不是 15 个字节!
>>> s[2]
'树'
>>> s[0:2]
'你好'
>>> list(s)
['你', '好', '树', '莓', '派']
Python 3 的字符串是按"字符"算的,不是按字节算的——编码转换(UTF-8 ↔ 字符)Python 在背后悄悄处理掉了。
所以:以后处理中文文本,用 Python 就是对的。 想按字切、想数有几个字、想反转中文句子(s[::-1] 得到 派莓树好你),都不会乱码。
这一条你以后会感恩很久——写处理文本的小工具,Python 能省掉一半的坑。
六、动手 Demo:一个真正的待办清单
把本节所有东西串起来。在命令行里写:
cd ~/projects
nano todo.py
内容:
todos = [] # 空列表:一开始什么事都没有
print("=== 待办清单 ===")
print("一行一条,直接回车就结束。\n")
while True:
task = input("任务:").strip() # 先清洗(去掉两头空格)
if not task: # 空字符串 → 结束录入
break
todos.append(task) # 追加到列表
if not todos: # 一条都没录
print("\n什么都没录,走了。")
else:
todos.sort() # 排个序,看着舒服
print(f"\n你一共列了 {len(todos)} 件事:")
for i, task in enumerate(todos, 1): # 带序号遍历
print(f" {i}. {task}")
print("\n最长的一条是:", max(todos, key=len))
print("平均每条", round(sum(len(t) for t in todos) / len(todos), 1), "个字")
跑:
python3 todo.py
输入三件事,看到什么算成功:
=== 待办清单 ===
一行一条,直接回车就结束。
任务:买菜
任务:遛狗
任务:交电费
任务:
你一共列了 3 件事:
1. 交电费
2. 买菜
3. 遛狗
最长的一条是: 交电费
平均每条 2.3 个字
读结果 —— 逐条对一遍:
| 输出 | 为什么 |
|---|---|
排序后是 交电费 / 买菜 / 遛狗 |
todos.sort() 按字符编码排;交(U+4EA4) < 买(U+4E70) < 遛(U+905B) ✅ |
| 序号从 1 开始 | enumerate(todos, 1) 那个 1 的作用 |
最长的是 交电费 |
交电费 是 3 个字,另外两条都是 2 个字,所以它最长(没有并列) |
| 平均 2.3 | 3 + 2 + 2 = 7 字,7 ÷ 3 = 2.333…,round(..., 1) 得 2.3 ✅ |
这个 Demo 用到了本章几乎所有知识点:
| 代码 | 来自哪一节 |
|---|---|
input().strip() |
6-6(方法链)、6-3(input 是字符串) |
if not task: break |
6-4(空字符串是假、break) |
todos.append(task) |
6-6(注意:没接返回值,对的) |
todos.sort() |
6-6 |
len(todos)、enumerate、max(..., key=len) |
6-6 |
f" {i}. {task}" |
6-3(f-string) |
round(...) |
6-3 |
意思是"把每个任务的字符数加起来"——这行用了"生成器表达式",本章不展开,你看得懂它在干什么就够了(本课后面讲到数据处理时会正式遇到)。写不出来不影响你读懂别人的代码。
七、30 秒实验
实验一:三种序列,规则完全一样
>>> lst = [10, 20, 30, 40]
>>> s = "abcd"
>>> t = (10, 20, 30, 40)
>>> lst[1], s[1], t[1]
(20, 'b', 20)
>>> lst[-1], s[-1], t[-1]
(40, 'd', 40)
>>> lst[1:3], s[1:3], t[1:3]
([20, 30], 'bc', (20, 30))
>>> lst[::-1], s[::-1], t[::-1]
([40, 30, 20, 10], 'dcba', (40, 30, 20, 10))
下标、负下标、切片、反转——三种一模一样。 学一套,用三处。
实验二:中文长度,直观对比
>>> s = "你好树莓派"
>>> len(s)
5 # Python 数的是"字"
>>> s[2]
'树'
>>> s[::-1]
'派莓树好你' # 中文也能干净地反转
对照 C++ 的印象:如果按 UTF-8 字节算,这串是 15 字节,s[2] 会取到"好"字的中间那一个字节 → 乱码。Python 按字符算,所以不会。
实验三:字符串方法返回新值,原串不动
>>> s = " hello "
>>> s.strip()
'hello'
>>> s
' hello ' # ← 原串纹丝不动
>>> s = s.strip() # 想生效,必须接回来
>>> s
'hello'
顺便对比列表:
>>> a = [3, 1]
>>> a.sort() # 原地改,别接返回值
>>> a
[1, 3]
一个必须"接"、一个千万别"接"——这就是本节最容易混的一对。
实验四:split / join 双向门
>>> "a,b,c".split(",")
['a', 'b', 'c']
>>> ",".join(['a', 'b', 'c'])
'a,b,c'
>>> "".join(['你', '好'])
'你好' # 空分隔符 = 直接粘起来
八、常见误区
- "
a = a.append(x)能更新列表" —— 不能,而且会把列表变成None。原地修改的方法返回None,直接调用就好,别接返回值。 - "字符串也能像列表那样改" —— 不能。字符串不可变,所有方法都返回新值。想生效必须
s = s.strip()这样接住(和上一条刚好相反,最容易混)。 - "切片
[1:4]会取到下标 4" —— 不会,含头不含尾,取的是 1、2、3。这条规则和range完全一致,记一次通用。 - "
len("你好")是 6" —— 那是字节数(C++ 的算法)。Python 返回 2(字符数)。Python 处理中文不会乱码,这是它相对 C++ 的一大优势。 - "排序把大写和小写混着排" —— 不是。默认按字符编码排,大写字母(65~90)全在小写(97~122)前面,所以
Apple会排在banana前面。要忽略大小写:sort(key=str.lower)。 - "
"".join(列表)能用,那列表.join("")应该也行" —— 反了。join是字符串的方法,不是列表的。只有一种写法:"分隔符".join(列表)(分隔符写前面、join挂在它身上)。写成列表.join(...)会报AttributeError: 'list' object has no attribute 'join'。 - "
input()拿到的就是干净的内容" —— 可能带两头空格。永远记得.strip(),比较文字时再顺手.lower()("用户输入不可信"是这一章反复出现的主题)。
自测三问(点开对答案)
1. `[10, 20, 30, 40, 50][1:4]` 得到什么?为什么? 2. `todos.append("买菜")` 和 `todos = todos.append("买菜")`,哪个对?为什么? 3. `len("你好树莓派")` 在 Python 里返回几?如果在 C++ 里按 UTF-8 算会是几? **参考答案** 1. **`[20, 30, 40]`**。切片是**含头不含尾**:从下标 1 开始、到下标 4 之前停,所以取 1、2、3 三个。**这条规则和 `range(1, 4)` 完全一致。** 2. **第一种对**。`.append()` 是**原地修改**,直接改列表本身,**返回 `None`**——第二种写法等于把 `None` 赋给了 `todos`,**整个列表就没了**。(规律:`append`/`remove`/`sort`/`reverse`/`clear` 这类"原地改"的方法,**一律别接返回值**。) 3. **Python 返回 5**(数的是字符);**按 UTF-8 字节算会是 15**(一个汉字 3 字节)。**这正是 Python 处理中文不乱码的原因——它按字符索引,不按字节。**- ☑ 第六章 完结 —— 教材这一章已读完全部六节:认清 Python 是什么、学会在树莓派上写和跑程序、掌握变量与运算符、学会用流程控制让程序会判断会循环、用函数与模块整理代码并装库,最后能处理列表与字符串
- ☑ 第七章 GPIO 硬件接口 —— 已完结:Sense HAT 扩展板(让代码第一次碰到现实世界:认清 40 针引脚、拿到 gpiozero 这个工具、点亮 LED 也读通按钮、用 PWM 做出「半亮」、外接 ADC 芯片读模拟量,最后用 Sense HAT 把灯与传感器一次装好)
6-1 认识它 → 6-2 会在机器上写它 → 6-3 给它数据 → 6-4 给它判断力和耐力 → 6-5 给它结构 → 6-6 给它批量处理数据的能力。 走完这一章,你手上终于有了在树莓派上"自己造东西"的工具。 这也是本课的一个转折点:前面五章你一直在"把机器搭起来、把服务装上去";从这一章起,你可以开始写自己的程序了。
相关笔记
- Python 函数与模块 —— 上一节:本节的
enumerate用了解包、max(key=...)用了函数作参数 - 认识树莓派的 GPIO 引脚 —— 下一章起点:从这排 40 针开始,让代码第一次碰到现实世界
- Sense HAT 扩展板 —— 下一章小结:一块叠上去的板子,把彩灯、摇杆与传感器一次装好
- Python 流程控制 —— 本节
for task in todos:是那里讲的for的真正用武之地 - 字符数组 —— 对照重点:那里踩过"UTF-8 下按字节切汉字出乱码"的坑,Python 没有这个问题
- 一维数组 —— 数组的对照;C++ 数组长度固定、Python 列表能自由增删
- Python 变量与运算符 —— 字符串拼接、f-string、
input()要转换,都在那里 - 🍓 树莓派 · 索引 —— 全课路线图