入门期核心理念:以代码为镜,理解Python设计哲学
温馨提示1:本篇仅仅是记录个人的学习历程,会有不少片面的理解,万分期待您的指教。
温馨提示2:为了知识的完整性可以看看前篇的内容哦
Python 序列:从基础到实践
序列(Sequence)是 Python 中最基础且最重要的数据结构之一。它表示一组有序的元素,支持索引、切片、迭代等操作。
第一步:强化基础,打牢根基
默认大家都有一定的Python基础了哦
1. 序列是什么?
“序列就像一排盒子,每个盒子里都装了一个东西(元素),而且每个盒子都有一个编号(索引)。你可以通过编号找到某个盒子里的东西,也可以对盒子里的东西进行各种操作,比如添加、删除、替换或者重新排列。”
根据 Python 官方文档,序列需满足以下条件:
有序性:元素按特定顺序排列。
可索引:通过整数索引(如
s[0]
)访问元素。可迭代:可通过
for
循环遍历元素。支持长度计算:
len(s)
返回元素数量。
Python 内置的常见序列类型包括:
-
列表(list):可变的动态数组。
-
元组(tuple):不可变的静态数组。
-
字符串(str):字符的不可变序列。
-
字节数组(bytes/bytearray):处理二进制数据的序列。
2. 基础用法
2.1 列表(list)
①如何构造一个列表?
-
list()
:创建一个空列表或从可迭代对象创建列表。empty_list = list() # 创建空列表 list_from_iterable = list([1, 2, 3]) # 从可迭代对象创建列表
②列表方法总结
(介个是必须要掌握滴,魔笛商量(语气强硬))
方法名 | 描述 | 示例 |
---|---|---|
append(x) | 在列表末尾添加一个元素 x 。 | lst.append(4) → [1, 2, 3, 4] |
extend(iterable) | 将可迭代对象 iterable 中的所有元素添加到列表末尾。 | lst.extend([4, 5]) → [1, 2, 3, 4, 5] |
insert(i, x) | 在索引 i 处插入元素 x 。 | lst.insert(1, 1.5) → [1, 1.5, 2, 3] |
remove(x) | 移除列表中第一个值为 x 的元素。 | lst.remove(2) → [1, 3, 2] |
pop([i]) | 移除并返回列表中索引为 i 的元素。 | lst.pop(1) → 2 ,lst 变为 [1, 3] |
clear() | 移除列表中的所有元素,使其变为空列表。 | lst.clear() → [] |
index(x[, start[, end]]) | 返回列表中第一个值为 x 的元素的索引。 | lst.index(2) → 1 |
count(x) | 返回列表中值为 x 的元素的个数。 | lst.count(2) → 2 |
sort(key=None, reverse=False) | 对列表中的元素进行排序。 | lst.sort() → [1, 1, 3, 4, 5, 9] |
reverse() | 反转列表中的元素顺序。 | lst.reverse() → [3, 2, 1] |
copy() | 返回列表的浅拷贝。 | lst_copy = lst.copy() → [1, 2, 3] |
添加元素
-
append(x)
:在列表末尾添加一个元素。my_list = [1, 2, 3] my_list.append(4) # my_list 现在是 [1, 2, 3, 4]
-
extend(iterable)
:将一个可迭代对象的所有元素添加到列表末尾。my_list = [1, 2, 3] my_list.extend([4, 5]) # my_list 现在是 [1, 2, 3, 4, 5]
-
insert(i, x)
:在指定位置插入一个元素。(从0开始)my_list = [1, 2, 3] my_list.insert(1, 'a') # my_list 现在是 [1, 'a', 2, 3]
删除元素
-
remove(x)
:删除列表中第一个值为x
的元素。如果x
不存在,会抛出ValueError
。my_list = [1, 2, 3, 2] my_list.remove(2) # my_list 现在是 [1, 3, 2]
-
pop([i])
:删除并返回指定位置的元素。如果不指定位置,默认删除并返回最后一个元素。my_list = [1, 2, 3] my_list.pop() # 返回 3,my_list 现在是 [1, 2] my_list.pop(0) # 返回 1,my_list 现在是 [2]
-
clear()
:清空列表。my_list = [1, 2, 3] my_list.clear() # my_list 现在是 []
查找元素
-
index(x[, start[, end]])
:返回列表中第一个值为x
的元素的索引。可以指定搜索的起始和结束位置。my_list = [1, 2, 3, 2] my_list.index(2) # 返回 1 my_list.index(2, 2) # 返回 3
-
count(x)
:返回列表中值为x
的元素的数量。my_list = [1, 2, 3, 2] my_list.count(2) # 返回 2
排序和反转
-
sort(*, key=None, reverse=False)
:对列表进行原地排序。my_list = [3, 1, 2] my_list.sort() # my_list 现在是 [1, 2, 3] my_list.sort(reverse=True) # my_list 现在是 [3, 2, 1]
-
reverse()
:对列表进行原地反转。my_list = [1, 2, 3] my_list.reverse() # my_list 现在是 [3, 2, 1]
复制列表
-
copy()
:返回列表的一个浅拷贝。my_list = [1, 2, 3] my_list_copy = my_list.copy() # my_list_copy 是 [1, 2, 3]
2.2 元组(tuple)
①如何构造一个元组?
-
tuple()
:创建一个空元组或从可迭代对象创建元组。(创建单个元素的元组时,“,”要记得)empty_tuple = tuple() # 创建空元组 tuple_from_iterable = tuple([1, 2, 3]) # 从可迭代对象创建元组
②如何访问元组?(支持索引和切片)
元组支持通过索引访问元素,但不能修改元素。
my_tuple = (1, 2, 3)
print(my_tuple[0]) # 输出:1
print(my_tuple[1:3]) # 输出:(2, 3)
③元组方法总结
方法名 | 描述 | 示例 |
---|---|---|
count(x) | 返回元组中值为 x 的元素的个数。 | tup.count(2) → 3 |
index(x[, start[, end]]) | 返回元组中第一个值为 x 的元素的索引。 | tup.index(2) → 1 |
元组支持以下方法:
-
count(x)
:返回元组中值为x
的元素的数量。my_tuple = (1, 2, 3, 2) print(my_tuple.count(2)) # 输出:2
-
index(x[, start[, end]])
:返回元组中第一个值为x
的元素的索引。可以指定搜索的起始和结束位置。my_tuple = (1, 2, 3, 2) print(my_tuple.index(2)) # 输出:1 print(my_tuple.index(2, 2)) # 输出:3
2.3 元组与列表的区别
特性 | 元组 (Tuple) | 列表 (List) |
---|---|---|
可变性 | 不可变 | 可变 |
语法 | 使用圆括号 () | 使用方括号 [] |
性能 | 存储和访问速度更快 | 存储和访问速度较慢 |
适用场景 | 存储不可变数据,如常量、配置项等 | 存储需要频繁修改的数据 |
方法数量 | 仅支持 count 和 index 两个方法 | 支持多种方法(如 append , remove 等) |
2.4 所有序列类型均支持的操作
操作 | 示例 | 说明 |
---|---|---|
索引访问 | s[0] | 获取第一个元素 |
切片 | s[1:4] | 获取子序列(左闭右开) |
拼接 | s1 + s2 | 合并两个序列 |
重复 | s * 3 | 重复序列元素 |
成员检测 | x in s | 检查元素是否存在 |
长度计算 | len(s) | 返回元素数量 |
最小值/最大值/求和 | min(s) , max(s) , sum(s) | 数值型序列专用 |
第二步:探索进阶
1.列表(list)
1.1 列表推导式(List Comprehensions)
请对比以下这两种方式,判断哪一种更加简洁高效地创建列表?
例子1:
#比如说我需要1到10的平方数
squares = []
for x in range(1,11):
squares.append(x**2)
print(squares)
#用列表推导式
squares_list=[x**2 for x in range(1,11)]
print(squares_list)
输出如下:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
例子2(包含过滤条件):
#笛卡尔积[1,2,3] [4,5,6]
a=[1,2,3]
b=[4,5,6]
#求a b的笛卡尔积
cartesian_product=[]
#传统写法
for x in a:
for y in b:
cartesian_product.append((x,y))
print(cartesian_product)
#列表推导式
cartesian_product_list=[(x,y) for x in a for y in b]
print(cartesian_product_list)
#如果需要求a与自身不相等元素的组合
combs=[]
for x in a:
for y in a:
if x!=y:
combs.append((x,y))
print(combs)
combs_list=[(x,y) for x in a for y in a if x!=y]#x!=y过滤条件
print(combs_list)
输出如下:
[(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]
[(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
1.2 嵌套列表(Nested lists)
列表可以嵌套,形成多维列表。
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10,11,12]
]
温馨小贴士:matrix的元素是4个包含三个元素的列表
例子1:
#如何将矩阵转置
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10,11,12]
]
transposed = []
for i in range(len(matrix[0])):
row = [col[i] for col in matrix]
transposed.append(row)
print(transposed)
#等价于
transposed_list = [[row[i] for row in matrix ] for i in range(len(matrix[0]))]#从外向里看
print(transposed_list)
输出结果:
[[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]]
[[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]]
1.3 生成器表达式(Generator Expressions)
虽然列表推导式也可以生成元组、数组或其他类型的序列,但是生成器表达式占用的内存更少,因为生成器表达式使用迭代器协议逐个产出项,而不是构建整个列表提供给其他构造函数。生成器表达式的句法跟列表推导式几乎一样,只不过把方括号换成圆括号而已。
简而言之就是:内存高效的惰性求值
生成器表达式与列表推导式的对比
# 列表推导式
list_comp = [x * x for x in range(10)]
# 生成器表达式
gen_exp = (x * x for x in range(10))
print("列表推导式:", list_comp)
print("生成器表达式:", gen_exp)
# 使用生成器
print("生成器表达式的值:")
for value in gen_exp:
print(value)
输出:
列表推导式: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
生成器表达式: <generator object <genexpr> at 0x7f8b1c1e2f20>
生成器表达式的值:
0
1
4
9
16
25
36
49
64
81
1.4 动手实践:用列表实现堆栈(LIFO)
堆栈是一种遵循 后进先出(LIFO, Last In First Out) 原则的数据结构,即最后入栈的元素会最先出栈。(列表方法使得将列表用作栈非常容易)
堆栈的基本操作:
-
入栈(Push):将元素添加到堆栈的顶部。
-
出栈(Pop):移除并返回堆栈顶部的元素。
-
查看栈顶元素(Peek/Top):返回堆栈顶部的元素,但不移除它。
-
判断堆栈是否为空(Is Empty):检查堆栈是否为空。
-
获取堆栈大小(Size):返回堆栈中元素的数量。
用列表实现堆栈:
Python 的列表已经提供了实现堆栈所需的基本操作:
-
使用
append()
方法实现 入栈。 -
使用
pop()
方法实现 出栈。 -
使用
list[-1]
访问 栈顶元素。 -
使用
len(list)
获取堆栈大小。 -
使用
not list
判断堆栈是否为空。
以下是具体的实现代码:
class Stack:
def __init__(self):
self.stack = [] # 使用列表存储堆栈元素
def push(self, item):
"""入栈操作:将元素添加到堆栈顶部"""
self.stack.append(item)
def pop(self):
"""出栈操作:移除并返回堆栈顶部的元素"""
if not self.is_empty():
return self.stack.pop()
else:
raise IndexError("Pop from an empty stack")
def peek(self):
"""查看栈顶元素:返回堆栈顶部的元素,但不移除它"""
if not self.is_empty():
return self.stack[-1]
else:
raise IndexError("Peek from an empty stack")
def is_empty(self):
"""判断堆栈是否为空"""
return len(self.stack) == 0
def size(self):
"""获取堆栈大小"""
return len(self.stack)
def __str__(self):
"""返回堆栈的字符串表示"""
return str(self.stack)
# 测试堆栈实现
if __name__ == "__main__":
stack = Stack()
# 入栈操作
stack.push(10)
stack.push(20)
stack.push(30)
print("堆栈内容:", stack) # 输出: [10, 20, 30]
# 查看栈顶元素
print("栈顶元素:", stack.peek()) # 输出: 30
# 出栈操作
print("出栈元素:", stack.pop()) # 输出: 30
print("堆栈内容:", stack) # 输出: [10, 20]
# 判断堆栈是否为空
print("堆栈是否为空:", stack.is_empty()) # 输出: False
# 获取堆栈大小
print("堆栈大小:", stack.size()) # 输出: 2
说明一下:
-
push(item)
:将元素添加到堆栈的末尾(即栈顶)。 -
pop()
:移除并返回堆栈的最后一个元素(即栈顶元素)。如果堆栈为空,抛出异常。 -
peek()
:返回堆栈的最后一个元素(即栈顶元素),但不移除它。如果堆栈为空,抛出异常。 -
is_empty()
:检查堆栈是否为空。 -
size()
:返回堆栈中元素的数量。 -
__str__()
:返回堆栈的字符串表示,方便打印堆栈内容。(魔术方法)
输出结果:
堆栈内容: [10, 20, 30]
栈顶元素: 30
出栈元素: 30
堆栈内容: [10, 20]
堆栈是否为空: False
堆栈大小: 2
小结一下:
-
使用 Python 的列表可以轻松实现堆栈数据结构。
-
堆栈的核心操作是 入栈(Push) 和 出栈(Pop),分别对应列表的
append()
和pop()
方法。
2.元组(tuple)
“元组不仅仅是不可变的列表。它们应该被用作数据的记录,其中每个元素代表数据的一个字段,而整个元组代表一个完整的记录。”
—— Luciano Ramalho, 《Fluent Python, 2nd Edition》
2.1 元组作为记录
元组可以作为记录使用,每个元素代表记录中的一个字段。
-
示例:
record = ('Alice', 30, 'New York') name, age, city = record print(name, age, city) # 输出:Alice 30 New Yor #元组作为记录,记录学生信息(姓名,学号,地址)(纯虚构不具备现实意义) students=[('肘子',20250909,'斗气大陆'),('AL',20250808,'TG'),('DL',20250907,'HJ')] print(students) for stu in sorted(students): print('%s-%s-%s'% stu) for name,_,_ in students: print(name)
输出:
[('肘子', 20250909, '斗气大陆'), ('AL', 20250808, 'TG'), ('DL', 20250907, 'HJ')]
AL-20250808-TG
DL-20250907-HJ
肘子-20250909-斗气大陆
肘子
AL
DL
2.2 元组作为不可变的列表(针对的是元组中的引用而言是不可变的)
元组的不可变性使得它在某些场景下非常有用,例如作为字典的键。
-
示例:
my_dict = {(1, 2): "value1", (3, 4): "value2"} print(my_dict[(1, 2)]) # 输出:value1
2.3 元组解包(Tuple Unpacking)
元组解包是一种强大的特性,允许将元组中的值赋给多个变量。解包不仅限于元组,还可以应用于任何可迭代对象。
-
基本解包:
point=(1,3) x,y=point print(x,y)#输出1 3
-
嵌套解包:
metro_areas = [ ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)), ] for name, cc, pop, (lat, lon) in metro_areas: print(f"{name:15} | {lat:9.4f} | {lon:9.4f}")
-
解包与占位符: 使用
_
作为占位符,可以忽略不需要的值。_, filename = os.path.split('/home/user/file.txt') print(filename) # 输出:file.txt for name,_,_ ,_ in metro_areas: print(name) ''' 输出结果: Tokyo Delhi NCR '''
-
解包与剩余元素: 使用
*
可以捕获剩余的元素。a, *body, c = range(5) print(a, body, c) # 输出:0 [1, 2, 3] 4 for name,*others in metro_areas: print(others) ''' 输出结果: ['JP', 36.933, (35.689722, 139.691667)] ['IN', 21.935, (28.613889, 77.208889)] '''
2.4 * 拆包的应用 (在函数调用和序列字面量中使用 * 拆包)
2.4.1. 在函数调用中使用 *
拆包
函数参数解包
当你有一个可迭代对象(如列表或元组)时,可以使用 *
将其解包为多个参数传递给函数。
def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
result = add(*numbers) # 解包列表为参数
print(result) # 输出:6
在这个例子中,*numbers
将列表中的每个元素解包为单独的参数,传递给 add
函数。
函数返回值解包
函数可以返回多个值,这些值可以被解包到多个变量中。
def get_user_info():
return "Alice", 30, "New York"
name, age, city = get_user_info() # 解包返回值
print(name, age, city) # 输出:Alice 30 New York
解包与默认参数
在函数调用中,可以结合默认参数和解包操作。
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
info = ("Bob",)
print(greet(*info)) # 输出:Hello, Bob!
解包与关键字参数
在函数调用中,可以使用 **
解包字典为关键字参数。
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
info = {"name": "Alice", "greeting": "Hi"}
print(greet(**info)) # 输出:Hi, Alice!
2.4.2 在序列字面量中使用 *
拆包
解包到序列
在序列字面量中,可以使用 *
将一个可迭代对象解包到另一个序列中。
a = [1, 2, 3]
b = [4, 5, 6]
combined = [*a, *b] # 解包两个列表到一个新的列表
print(combined) # 输出:[1, 2, 3, 4, 5, 6]
解包到元组
同样,可以将多个可迭代对象解包到元组中。
a = (1, 2, 3)
b = (4, 5, 6)
combined = (*a, *b) # 解包两个元组到一个新的元组
print(combined) # 输出:(1, 2, 3, 4, 5, 6)
解包到集合
在集合中,可以使用 *
解包多个可迭代对象。
a = {1, 2, 3}
b = {4, 5, 6}
combined = {*a, *b} # 解包两个集合到一个新的集合
print(combined) # 输出:{1, 2, 3, 4, 5, 6}
解包到字典
在字典中,可以使用 **
解包多个字典。
a = {"name": "Alice", "age": 30}
b = {"city": "New York", "country": "USA"}
combined = {**a, **b} # 解包两个字典到一个新的字典
print(combined) # 输出:{'name': 'Alice', 'age': 30, 'city': 'New York', 'country': 'USA'}
2.4.3 实际应用场景
函数调用
在调用函数时,解包可以简化代码,避免手动传递每个参数。
def process_data(data):
print(f"Processing {data}")
data = [1, 2, 3, 4]
process_data(*data) # 解包列表为参数
序列操作
在序列操作中,解包可以动态地组合多个序列。
a = [1, 2, 3]
b = [4, 5, 6]
combined = [*a, *b] # 解包两个列表到一个新的列表
print(combined) # 输出:[1, 2, 3, 4, 5, 6]
字典操作
在字典操作中,解包可以动态地合并多个字典。
a = {"name": "Alice", "age": 30}
b = {"city": "New York", "country": "USA"}
combined = {**a, **b} # 解包两个字典到一个新的字典
print(combined) # 输出:{'name': 'Alice', 'age': 30, 'city': 'New York', 'country': 'USA'}
2.4.4. 小结一下
*
拆包是一种非常强大的特性,它允许你在函数调用和序列字面量中动态地处理可变数量的元素。通过合理使用解包操作,可以简化代码,提高代码的可读性和灵活性哦。
-
函数调用:使用
*
解包可迭代对象为函数参数。 -
序列操作:使用
*
解包多个可迭代对象到一个新的序列。 -
字典操作:使用
**
解包多个字典到一个新的字典。 -
捕获剩余元素:使用
*
捕获解包操作中的剩余元素。
3 序列模式匹配(Sequence pattern matching,3.10+)
3.1 什么是match?
1. 用一句话说清楚
match
是什么
match
是 Python 3.10 引入的一种 模式匹配工具,它可以根据数据的值、类型或结构,选择执行不同的代码分支。2. 为什么需要
match
?在 Python 3.10 之前,我们通常用
if-elif-else
或字典分派来处理条件逻辑。但这些方式在处理复杂数据结构时,代码会变得冗长且难以维护。match
的引入解决了以下问题:
简化代码:用更直观的方式匹配数据结构。
提高可读性:模式匹配的语法更接近自然语言。
支持复杂匹配:可以匹配嵌套结构、类型、值等。
3.
match
的核心思想
match
的核心思想是 “匹配模式”。你可以把它想象成一个“智能开关”,它会根据数据的形状或值,自动选择正确的处理方式。想象你有一个盒子,里面可能装的是水果、书或玩具。
match
就像是一个自动分类器:
如果盒子里是水果,它会告诉你“这是水果”。
如果盒子里是书,它会告诉你“这是书”。
如果盒子里是其他东西,它会告诉你“我不知道这是什么”。
4.
match
的基本语法
match
语句的基本结构如下:match subject: case pattern1: # 处理 pattern1 case pattern2: # 处理 pattern2 case _: # 默认情况(类似于 else)match subject: case pattern1: # 处理 pattern1 case pattern2: # 处理 pattern2 case _: # 默认情况(类似于 else)
subject
:要匹配的目标对象。
pattern
:匹配模式,可以是值、类型、结构等。
_
:通配符,匹配任意值(类似于else
)。5.
match
的常见用法(1) 匹配值
最简单的用法是匹配具体的值:
def http_status(status): match status: case 200: return "OK" case 404: return "Not Found" case 500: return "Server Error" case _: return "Unknown Status" print(http_status(200)) # 输出: OK print(http_status(404)) # 输出: Not Found
(2) 匹配类型
可以匹配对象的类型:
def process_data(data): match data: case int(): return f"整数: {data}" case str(): return f"字符串: {data}" case list(): return f"列表: {data}" case _: return "未知类型" print(process_data(42)) # 输出: 整数: 42 print(process_data("hello")) # 输出: 字符串: hello print(process_data([1, 2, 3])) # 输出: 列表: [1, 2, 3]
(3) 匹配结构
可以匹配复杂的数据结构,比如元组、列表、字典等:
def process_point(point): match point: case (0, 0): return "原点" case (x, 0): return f"X轴上的点: {x}" case (0, y): return f"Y轴上的点: {y}" case (x, y): return f"普通点: ({x}, {y})" case _: return "无效的点" print(process_point((0, 0))) # 输出: 原点 print(process_point((3, 0))) # 输出: X轴上的点: 3 print(process_point((0, 4))) # 输出: Y轴上的点: 4 print(process_point((2, 3))) # 输出: 普通点: (2, 3)
(4) 匹配嵌套结构
match
支持匹配嵌套的数据结构:def process_nested(data): match data: case {"name": str(name), "age": int(age)}: return f"用户: {name}, 年龄: {age}" case [1, [2, 3]]: return "匹配嵌套列表: [1, [2, 3]]" case _: return "未知结构" print(process_nested({"name": "Alice", "age": 30})) # 输出: 用户: Alice, 年龄: 30 print(process_nested([1, [2, 3]])) # 输出: 匹配嵌套列表: [1, [2, 3]]
(5) 匹配时绑定变量
可以在匹配时将值绑定到变量:
def process_data(data): match data: case {"name": name, "age": age}: return f"姓名: {name}, 年龄: {age}" case [x, y, z]: return f"列表元素: {x}, {y}, {z}" case _: return "未知数据" print(process_data({"name": "Bob", "age": 25})) # 输出: 姓名: Bob, 年龄: 25 print(process_data([1, 2, 3])) # 输出: 列表元素: 1, 2, 3
(6) 匹配时添加条件(Guard)
可以在
case
中添加if
条件,进一步过滤匹配:def process_number(n): match n: case x if x < 0: return "负数" case x if x == 0: return "零" case x if x > 0: return "正数" print(process_number(-5)) # 输出: 负数 print(process_number(0)) # 输出: 零 print(process_number(10)) # 输出: 正数
6.
match
的底层原理
match
的实现基于 模式匹配引擎,它会按顺序尝试匹配每个case
,直到找到第一个匹配的模式。匹配时,Python 会检查以下内容:
值的相等性:比如
case 200
匹配200
。类型的匹配:比如
case int()
匹配整数。结构的匹配:比如
case [x, y]
匹配长度为 2 的列表。条件的匹配:比如
case x if x > 0
匹配满足条件的值。
3.2. 嵌套序列模式
解构序列内容:
def match_sequence(seq):
match seq:
case [first, second, *rest]:
print(f"前两个元素: {first}, {second}, 剩余: {rest}")
case (x, y) if x == y:
print("元组元素相同")
case _:
print("其他情况")
match_sequence([1, 2, 3, 4]) # 输出: 前两个元素: 1, 2, 剩余: [3, 4]
match_sequence((5, 5)) # 输出: 元组元素相同
4. 序列切片
4.1 用一句话说清楚「序列切片」是什么
序列切片是 Python 中一种 从序列(如列表、字符串、元组)中提取子序列的简洁语法,通过指定起始、结束位置和步长,快速截取数据片段。
4.2 为什么需要切片?
想象有一本书,但你只想阅读第 50 到 100 页的内容。切片就像一把剪刀,帮你快速剪出想要的片段,而无需手动复制每一页。
切片的优势:
-
简洁性:用一行代码提取子序列。
-
灵活性:支持正负索引、步长控制。
-
非破坏性:原序列保持不变,生成新对象。
4.3 切片的核心语法
切片语法为 sequence[start:stop:step]
,其中:
-
start
:起始索引(包含)。 -
stop
:结束索引(不包含)。 -
step
:步长(默认为1,正数从左到右,负数从右到左)。 -
如果序列是一列火车车厢,切片就是选择从第
start
节到第stop
节车厢,每隔step
节取一次。
4.4. 切片的具体用法
(1) 基本切片
text = "ABCDEFG"
print(text[1:4]) # 输出: BCD(索引1到3)
print(text[:3]) # 输出: ABC(从头到索引2)
print(text[3:]) # 输出: DEFG(从索引3到末尾)
(2) 负索引
nums = [0, 1, 2, 3, 4, 5]
print(nums[-3:]) # 输出: [3, 4, 5](倒数第3个到末尾)
print(nums[-5:-2]) # 输出: [1, 2, 3]
(3) 步长控制
letters = ["A", "B", "C", "D", "E", "F"]
print(letters[::2]) # 输出: ['A', 'C', 'E'](每隔一个取一个)
print(letters[::-1]) # 输出: ['F', 'E', 'D', 'C', 'B', 'A'](反转序列)
(4) 切片赋值(仅可变序列)
lst = [1, 2, 3, 4, 5]
lst[1:4] = [20, 30] # 替换索引1到3的元素为[20, 30]
print(lst) # 输出: [1, 20, 30, 5]
4.5 切片的底层原理
切片操作实际上是调用了序列的 __getitem__
方法,并传入一个 slice
对象。例如:
# 以下两行代码是等价的
text[1:4:2]
text.__getitem__(slice(1, 4, 2))
关键点:
-
不修改原序列:切片会生成一个新对象。
-
越界自动处理:若索引超出范围,Python 会自动调整到有效范围。
-
浅拷贝:切片是原序列元素的引用(对可变对象需注意副作用)。
4.6 切片的应用场景
(1) 提取子序列
# 提取字符串中的日期部分
date_str = "2023-10-01"
year = date_str[:4] # "2023"
month = date_str[5:7] # "10"
(2) 反转序列
s = "hello"
reversed_s = s[::-1] # "olleh"
(3) 分批处理数据
data = [1, 2, 3, 4, 5, 6, 7, 8]
batch1 = data[:4] # 第一批: [1, 2, 3, 4]
batch2 = data[4:] # 第二批: [5, 6, 7, 8]
(4) 删除或替换部分内容
# 删除列表中的偶数
lst = [1, 2, 3, 4, 5, 6]
lst[1::2] = [] # 结果: [1, 3, 5]
4.7 切片的注意事项
-
左闭右开区间:
sequence[start:stop]
包含start
,不包含stop
。 -
默认值:
-
start
默认为0
(从头开始)。 -
stop
默认为序列长度(到末尾结束)。 -
step
默认为1
。
-
-
步长为负数时:
start
应大于stop
(反向截取)。
“切片就像从披萨中切出一块你喜欢的部分,剩下的披萨完好无损,而你可以随意享用切下来的那块。”
4.9 切片对象
1. 切片对象是什么?
切片对象(slice
)是一个包含 start
、stop
和 step
参数的不可变对象,用于表示切片操作的逻辑。当你在代码中编写 s[a:b:c]
时,Python 会隐式创建一个 slice(a, b, c)
对象,并将其传递给序列的 __getitem__
或 __setitem__
方法。
示例:
# 以下两行代码等价
s = "ABCDEFG"
print(s[1:5:2]) # 输出: "BD"
print(s.__getitem__(slice(1, 5, 2))) # 输出: "BD"
2. 切片对象的创建与属性
切片对象可以通过 slice()
构造函数显式创建:
# 语法:slice(stop) | slice(start, stop[, step])
s1 = slice(3) # 等效于 0:3:1
s2 = slice(1, 5, 2) # 等效于 1:5:2
s3 = slice(None, 10) # 等效于 :10:1
属性:
-
.start
:起始索引(默认为None
)。 -
.stop
:结束索引(默认为None
)。 -
.step
:步长(默认为None
)。
print(s2.start, s2.stop, s2.step) # 输出: 1 5 2
3. 切片对象的底层原理(强化理解)
当对序列(如列表、字符串)进行切片时,Python 内部会调用 __getitem__
方法,并将 slice
对象作为参数传递。序列需要根据 slice
对象的参数返回对应的子序列。
自定义类的切片支持:
以下是一个支持切片的自定义 Vector
类的简化实现:
class Vector:
def __init__(self, components):
self._components = list(components)
def __len__(self):
return len(self._components)
def __getitem__(self, index):
# 处理切片对象
if isinstance(index, slice):
return Vector(self._components[index])
# 处理单个索引
else:
return self._components[index]
v = Vector([1, 2, 3, 4, 5])
print(v[1:4]) # 输出: Vector([2, 3, 4])
4. 切片对象的边界处理
切片操作会自动处理越界索引,无需手动检查范围。Python 的切片设计遵循“宽容处理越界”原则,
lst = [1, 2, 3]
print(lst[:10]) # 输出: [1, 2, 3](自动调整 stop 为 3)
5. 多维切片与高级用法
切片对象可以用于多维数据结构(如 NumPy 数组),但需要自定义类支持多维切片逻辑。
__getitem__
可以接收多个切片对象(以元组形式传递),例如:
# 假设一个二维数组类
class Matrix:
def __getitem__(self, index):
# index 可能是一个元组,如 (slice(0,2), slice(1,3))
row_slice, col_slice = index
# 处理行和列的切片
# ...
6. 切片对象的实际应用(让代码更加清晰)
(1) 动态生成切片(比如说要处理发票单之类的固定文本)
切片对象可以作为参数传递或动态生成:
def dynamic_slice(data, start, stop, step):
return data[slice(start, stop, step)]
lst = [0, 1, 2, 3, 4, 5]
print(dynamic_slice(lst, 1, 5, 2)) # 输出: [1, 3]
(2) 反向迭代
利用负步长切片实现反向遍历:
s = "hello"
reverse_slice = slice(None, None, -1)
print(s[reverse_slice]) # 输出: "olleh"
7. 切片对象的注意事项
-
浅拷贝:切片操作生成的是原序列元素的引用(对可变对象需注意副作用)。
-
不可变性:切片对象本身不可修改,但可以创建新的切片对象。
-
自定义类的兼容性:若自定义类需要支持切片,必须正确处理
slice
对象。
自己试一试:自定义序列类的完整切片支持
class MySeq:
def __init__(self, data):
self.data = list(data)
def __getitem__(self, index):
if isinstance(index, slice):
# 处理切片对象
start, stop, step = index.indices(len(self.data))
return [self.data[i] for i in range(start, stop, step)]
else:
return self.data[index]
seq = MySeq([0, 1, 2, 3, 4, 5])
print(seq[1:5:2]) # 输出: [1, 3]
5. 高级序列类型
5.1 数组(array
模块)
5.1.1 数组的核心特点
-
同质数据类型:数组存储相同类型的元素(如整数、浮点数),与列表(
list
)不同。 -
紧凑内存布局:元素在内存中连续存储,类似 C 语言的数组,内存占用远小于列表。
-
高效数值操作:支持快速数值计算(比列表快,但不如 NumPy)。
5.1.2 创建数组(类型码与类型约束)
数组的类型由 类型码(Type Code) 定义,例如:
-
'i'
:有符号整数(4字节) -
'f'
:单精度浮点数(4字节) -
'd'
:双精度浮点数(8字节)
import array
# 创建一个整数数组
int_arr = array.array('i', [1, 2, 3, 4]) # 类型码 'i'
print(int_arr) # array('i', [1, 2, 3, 4])
# 创建一个双精度浮点数数组
float_arr = array.array('d', [1.0, 2.5, 3.14])
print(float_arr) # array('d', [1.0, 2.5, 3.14])import array
5.1.3 数组的优势与局限性
-
优势:
-
内存效率高(存储
int
或float
时比列表节省 50% 以上内存)。 -
支持文件读写(
fromfile
/tofile
)和缓冲区协议。
-
-
局限性:
-
只能存储同质数据。
-
功能比列表简单(不支持多维、复杂操作)。
-
数值计算性能不如 NumPy。
-
5.1.4 数组与列表的性能对比
import array, sys
# 内存占用对比
lst = list(range(1000))
arr = array.array('i', lst)
print(sys.getsizeof(lst)) # ~9024 bytes(Python 3.10)
print(sys.getsizeof(arr)) # ~4024 bytes(节省 50%+)
5.2. memoryview
:零拷贝内存视图
5.2.1 什么是 memoryview
?
memoryview
是 Python 内置对象,允许通过 缓冲区协议(Buffer Protocol) 直接访问底层内存,无需复制数据。它适用于以下场景:
-
操作大型二进制数据(如图像、音频)。
-
与 C 扩展模块交互时避免数据拷贝。
-
高效处理
bytes
、bytearray
、array
等支持缓冲区协议的对象。
5.2.2 创建 memoryview
# 从字节序列创建 memoryview
data = bytearray(b'abcdefg')
mv = memoryview(data)
print(mv.tobytes()) # b'abcdefg'
# 修改 memoryview 会影响原始数据
mv[2] = 120 # ASCII 'x'
print(data) # bytearray(b'abxdefg')
5.2.3 memoryview
的核心特性
-
零拷贝:直接引用原始数据的内存。
-
多维支持:可表示多维数组(需指定形状和格式)。
-
切片共享内存:对
memoryview
切片时,新视图共享原始内存。
5.2.4 示例:使用 memoryview
操作数组
import array
arr = array.array('i', [1, 2, 3, 4, 5])
mv = memoryview(arr)
# 修改内存视图会影响原数组
mv[1:3] = array.array('i', [20, 30])
print(arr) # array('i', [1, 20, 30, 4, 5])
5.3. array
与 memoryview
的协同使用
5.3.1 结合 array
和 memoryview
实现高效 IO
import array
# 写入二进制文件
arr = array.array('d', [1.0, 2.5, 3.14])
with open('data.bin', 'wb') as f:
arr.tofile(f) # 高效写入二进制数据
# 从文件读取数据
arr_from_file = array.array('d')
with open('data.bin', 'rb') as f:
arr_from_file.fromfile(f, 3) # 读取3个元素
print(arr_from_file) # array('d', [1.0, 2.5, 3.14])
5.3.2 使用 memoryview
处理网络数据
import socket, array
# 接收网络数据(假设接收了4字节的整数)
data = bytearray(4)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('example.com', 80))
sock.recv_into(data) # 直接写入 bytearray
# 将 bytearray 转换为整数
mv = memoryview(data)
integer = array.array('i')
integer.frombytes(mv) # 零拷贝转换
print(integer[0])
5.4 官方文档要点总结
-
array
模块:
官方文档强调array
适用于存储同质数值数据,支持高效文件 IO 和缓冲区协议。
Python 官方文档 - array -
memoryview
:
官方文档指出memoryview
是缓冲区协议的接口,允许零拷贝操作内存,支持多维和结构化数据。
Python 官方文档 - memoryview
5.5 《Fluent Python》中的观点
-
array
的适用场景:
array
在处理数值数据时比列表更高效,但在复杂计算中应优先使用 NumPy。 -
缓冲区协议与跨语言交互:
缓冲区协议是 Python 与 C/C++ 扩展模块(如 NumPy)高效交互的基础。
5.6. 小结
-
array
:-
适用场景:存储同质数值数据,内存敏感型任务,简单文件 IO。
-
核心优势:内存紧凑,支持缓冲区协议。
-
-
memoryview
:-
适用场景:零拷贝操作二进制数据,多维数据处理,跨语言交互。
-
核心优势:避免数据复制,直接操作底层内存。
-
-
现代 Python 实践:
-
结合
array
和memoryview
实现高效数据存储与处理。 -
在需要复杂数值计算时,优先使用 NumPy(基于缓冲区协议扩展)。
-
动手试一试:高效图像处理
# 假设处理一个灰度图像(尺寸 1000x1000 像素)
import array
# 创建二维数组存储像素(0-255)
pixels = array.array('B', [0] * 1000 * 1000)
# 创建 memoryview 操作像素
mv = memoryview(pixels).cast('B', shape=(1000, 1000))
# 修改像素区域(无需复制数据)
mv[500:600, 500:600] = 255 # 设置一个白色方块
# 保存到文件
with open('image.bin', 'wb') as f:
pixels.tofile(f)
6.简述NumPy
6.1. NumPy的核心——ndarray
Numpy的核心是ndarray
(N-dimensional array,多维数组)。它是一个高效的数据结构,用于存储和操作多维数据。
6.1.1 什么是ndarray
?
-
ndarray
是一个多维数组,可以存储相同类型的元素(如整数、浮点数等)。 -
它是Numpy的基础数据结构,几乎所有Numpy操作都围绕它展开。
6.1.2 为什么需要ndarray
?
-
高效性:
ndarray
在底层用C语言实现,操作速度比Python列表快得多。 -
功能强大:支持向量化操作(对整个数组进行操作,而不是逐元素操作)。
-
广泛应用:在科学计算、数据分析、机器学习等领域中,
ndarray
是处理数据的标准工具。
6.2. Numpy的核心功能
6.2.1 创建数组
Numpy提供了多种创建数组的方式:
-
从Python列表创建:
import numpy as np arr = np.array([1, 2, 3, 4]) print(arr) # 输出: [1 2 3 4]
-
使用内置函数创建:
zeros = np.zeros((2, 2)) # 创建2x2的全零数组 ones = np.ones((3, 3)) # 创建3x3的全一数组 random_arr = np.random.random((2, 2)) # 创建2x2的随机数组
6.2.2 数组的属性
每个ndarray
都有一些重要的属性:
-
shape
:数组的形状(如(3, 3)
表示3行3列)。 -
dtype
:数组元素的数据类型(如int32
、float64
)。 -
size
:数组中元素的总数。 -
ndim
:数组的维度数。
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr.shape) # 输出: (2, 3)
print(arr.dtype) # 输出: int64
print(arr.size) # 输出: 6
print(arr.ndim) # 输出: 2
6.2.3 数组的操作
-
索引和切片:
arr = np.array([[1, 2, 3], [4, 5, 6]]) print(arr[0, 1]) # 输出: 2(第一行第二列) print(arr[:, 1]) # 输出: [2, 5](第二列的所有元素)
-
形状操作:
arr = np.arange(6).reshape(2, 3) # 将一维数组转换为2x3的二维数组 print(arr)
-
数学运算:
arr1 = np.array([1, 2, 3]) arr2 = np.array([4, 5, 6]) print(arr1 + arr2) # 输出: [5 7 9] print(np.dot(arr1, arr2)) # 输出: 32(点积)
6.2.4 广播机制
广播是Numpy的一种强大功能,允许不同形状的数组进行数学运算。例如:
arr = np.array([[1, 2, 3], [4, 5, 6]])
scalar = 2
print(arr + scalar) # 输出: [[3 4 5], [6 7 8]]
广播的规则是:从右到左逐元素比较形状,如果维度不匹配且其中一个数组的维度为1,则扩展该维度。
6.3. 发现问题并回顾
在学习过程中,可能会遇到以下问题:
-
为什么Numpy比Python列表快?
-
Numpy的底层是用C语言实现的,避免了Python循环的开销。
-
Numpy支持向量化操作,可以一次性处理整个数组,而不是逐元素操作。
-
-
广播机制的具体规则是什么?
-
广播规则的核心是形状对齐。如果两个数组的形状在某个维度上不匹配,且其中一个数组在该维度上的长度为1,则该数组会沿着该维度扩展以匹配另一个数组的形状。
-
-
如何高效地操作多维数组?
-
避免使用Python循环,尽量使用Numpy的向量化操作。
-
使用
reshape
、transpose
等函数来改变数组的形状,而不是创建新的数组。
-
6.4. 简化并类比理解
为了更简单地理解Numpy,可以用以下类比:
-
ndarray
就像一个表格:-
一维数组像一行数据。
-
二维数组像一个Excel表格。
-
三维数组像一堆Excel表格。
-
-
广播机制就像“复制粘贴”:
-
如果两个数组的形状不匹配,Numpy会自动“复制粘贴”较小的数组,使其形状匹配。
-
-
向量化操作就像“批量处理”:
-
例如,
arr + 1
相当于对数组中的每个元素都加1,而不需要写循环。
-