从零到一学习Pyhton(基础篇--入门期二序列)

入门期核心理念:以代码为镜,理解Python设计哲学

温馨提示1:本篇仅仅是记录个人的学习历程,会有不少片面的理解,万分期待您的指教。

温馨提示2:为了知识的完整性可以看看前篇的内容哦

从零到一学习Pyhton(基础篇--入门期一数据模型)-优快云博客

Python 序列:从基础到实践

序列(Sequence)是 Python 中最基础且最重要的数据结构之一。它表示一组有序的元素,支持索引、切片、迭代等操作。


第一步:强化基础,打牢根基 

默认大家都有一定的Python基础了哦

1. 序列是什么?

“序列就像一排盒子,每个盒子里都装了一个东西(元素),而且每个盒子都有一个编号(索引)。你可以通过编号找到某个盒子里的东西,也可以对盒子里的东西进行各种操作,比如添加、删除、替换或者重新排列。”

根据 Python 官方文档,序列需满足以下条件:

  1. 有序性:元素按特定顺序排列。

  2. 可索引:通过整数索引(如 s[0])访问元素。

  3. 可迭代:可通过 for 循环遍历元素。

  4. 支持长度计算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)在列表末尾添加一个元素 xlst.append(4) → [1, 2, 3, 4]
extend(iterable)将可迭代对象 iterable 中的所有元素添加到列表末尾。lst.extend([4, 5]) → [1, 2, 3, 4, 5]
insert(i, x)在索引 i 处插入元素 xlst.insert(1, 1.5) → [1, 1.5, 2, 3]
remove(x)移除列表中第一个值为 x 的元素。lst.remove(2) → [1, 3, 2]
pop([i])移除并返回列表中索引为 i 的元素。lst.pop(1) → 2lst 变为 [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 两个方法支持多种方法(如 appendremove 等)
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) 原则的数据结构,即最后入栈的元素会最先出栈。(列表方法使得将列表用作栈非常容易)

堆栈的基本操作:

  1. 入栈(Push):将元素添加到堆栈的顶部。

  2. 出栈(Pop):移除并返回堆栈顶部的元素。

  3. 查看栈顶元素(Peek/Top):返回堆栈顶部的元素,但不移除它。

  4. 判断堆栈是否为空(Is Empty):检查堆栈是否为空。

  5. 获取堆栈大小(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

说明一下:

  1. push(item):将元素添加到堆栈的末尾(即栈顶)。

  2. pop():移除并返回堆栈的最后一个元素(即栈顶元素)。如果堆栈为空,抛出异常。

  3. peek():返回堆栈的最后一个元素(即栈顶元素),但不移除它。如果堆栈为空,抛出异常。

  4. is_empty():检查堆栈是否为空。

  5. size():返回堆栈中元素的数量。

  6. __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. 小结一下

* 拆包是一种非常强大的特性,它允许你在函数调用和序列字面量中动态地处理可变数量的元素。通过合理使用解包操作,可以简化代码,提高代码的可读性和灵活性哦。

  • 函数调用:使用 * 解包可迭代对象为函数参数。

  • 序列操作:使用 * 解包多个可迭代对象到一个新的序列。

  • 字典操作:使用 ** 解包多个字典到一个新的字典。

  • 捕获剩余元素:使用 * 捕获解包操作中的剩余元素。

序列模式匹配Sequence pattern matching,3.10+

3.1 什么是match?

1. 用一句话说清楚 match 是什么

match 是 Python 3.10 引入的一种 模式匹配工具,它可以根据数据的值、类型或结构,选择执行不同的代码分支。

2. 为什么需要 match

在 Python 3.10 之前,我们通常用 if-elif-else 或字典分派来处理条件逻辑。但这些方式在处理复杂数据结构时,代码会变得冗长且难以维护。match 的引入解决了以下问题:

  1. 简化代码:用更直观的方式匹配数据结构。

  2. 提高可读性:模式匹配的语法更接近自然语言。

  3. 支持复杂匹配:可以匹配嵌套结构、类型、值等。

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 会检查以下内容:

  1. 值的相等性:比如 case 200 匹配 200

  2. 类型的匹配:比如 case int() 匹配整数。

  3. 结构的匹配:比如 case [x, y] 匹配长度为 2 的列表。

  4. 条件的匹配:比如 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 切片的注意事项
  1. 左闭右开区间sequence[start:stop] 包含 start,不包含 stop

  2. 默认值

    • start 默认为 0(从头开始)。

    • stop 默认为序列长度(到末尾结束)。

    • step 默认为 1

  3. 步长为负数时start 应大于 stop(反向截取)。

“切片就像从披萨中切出一块你喜欢的部分,剩下的披萨完好无损,而你可以随意享用切下来的那块。”

4.9 切片对象 
1. 切片对象是什么?

切片对象(slice)是一个包含 startstop 和 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. 切片对象的注意事项
  1. 浅拷贝:切片操作生成的是原序列元素的引用(对可变对象需注意副作用)。

  2. 不可变性:切片对象本身不可修改,但可以创建新的切片对象。

  3. 自定义类的兼容性:若自定义类需要支持切片,必须正确处理 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 扩展模块交互时避免数据拷贝。

  • 高效处理 bytesbytearrayarray 等支持缓冲区协议的对象。

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:数组元素的数据类型(如int32float64)。

  • 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的向量化操作。

    • 使用reshapetranspose等函数来改变数组的形状,而不是创建新的数组。


6.4. 简化并类比理解

为了更简单地理解Numpy,可以用以下类比:

  • ndarray就像一个表格

    • 一维数组像一行数据。

    • 二维数组像一个Excel表格。

    • 三维数组像一堆Excel表格。

  • 广播机制就像“复制粘贴”

    • 如果两个数组的形状不匹配,Numpy会自动“复制粘贴”较小的数组,使其形状匹配。

  • 向量化操作就像“批量处理”

    • 例如,arr + 1相当于对数组中的每个元素都加1,而不需要写循环。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愚戏师

缘分啊,道友

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值