py learning - day 1(列表、解包) 1.List的操作这里需明白sort()和sorted()的区别前者返回None,对原列表进行排序后者返回排序后列表不改变原列表2.解包场景 1基础赋值解包数量一一对应规则左边有几个变量右边就必须有几个元素按顺序一一对应。底层机制Python 虚拟机CEVAL直接通过 UNPACK_SEQUENCE 字节码将栈顶的序列按索引直接映射到变量无需创建中间容器。时间复杂度O(1)常数级。无论解包 3 个还是 5 个元素只要数量固定消耗的指令条数是恒定的。# 示例 1.1拆列表a,b,c[1,2,3]print(a)# 输出: 1print(b)# 输出: 2print(c)# 输出: 3# 示例 1.2拆字符串字符串也是排队对象x,y,zABCprint(x)# 输出: Aprint(y)# 输出: Bprint(z)# 输出: C在这里插入代码片场景 2扩展解包使用 * 星号吸收多余的元素规则带 * 的变量会变成一个“大胃王”把剩下的所有元素打包成一个列表吞掉。底层机制Python 必须先遍历右侧的可迭代对象计算出总数然后将未被单独匹配的元素显式创建为一个新的列表List 赋给带星号的变量。时间复杂度O(K)其中 K 是赋给带星号变量的元素数量。因为底层必须将这部分元素从源迭代器中拷贝到新的内存空间中本质是 list_extend 操作。如果源是生成器解包会强制将其完全消耗Exhaust这也是 O(N)。# 示例 2.1吸收中间或尾部head,*tail[10,20,30,40]print(head)# 输出: 10第一个元素print(tail)# 输出: [20, 30, 40]剩下的全变成列表# 示例 2.2取第一个和最后一个中间全打包first,*middle,last[1,2,3,4,5]print(first)# 输出: 1print(middle)# 输出: [2, 3, 4]注意中间被打包成列表print(last)# 输出: 5# 示例 2.3如果右边是空列表星号变量会变成空列表a,*b[100]print(a)# 输出: 100print(b)# 输出: []没东西可吃就是个空列表在这里插入代码片场景 3函数调用解包* 拆列表给位置参数拆字典给关键字参数规则把列表拆成“一个个单独的值”喂给函数把字典拆成“键值”的形式喂给函数。底层机制* 解包CPython 通过 PyObject_GetIter 获取迭代器然后循环调用 PyIter_Next 将每个元素压入调用栈Call Stack中。** 解包底层调用 PyDict_Next 遍历字典将键值对映射为关键字参数。时间复杂度O(N)其中 N 是被解包的元素/键值对数量。因为参数需要逐个被读取并压栈。# 3.1 定义一个需要三个参数的函数defintroduce(name,age,city):returnf姓名:{name}, 年龄:{age}, 城市:{city}# ----- 使用 * 拆列表按顺序对应-----person_list[张三,18,北京]result1introduce(*person_list)# 相当于执行 introduce(张三, 18, 北京)print(result1)# 输出: 姓名: 张三, 年龄: 18, 城市: 北京# ----- 使用 ** 拆字典按键名对应-----person_dict{city:上海,name:李四,age:20}# 注意顺序可以乱因为按名字匹配result2introduce(**person_dict)# 相当于执行 introduce(city上海, name李四, age20)print(result2)# 输出: 姓名: 李四, 年龄: 20, 城市: 上海# 额外演示如果直接把字典传进去不解包会报错# introduce(person_dict) # 报错因为只传了1个参数但函数要3个场景 4容器合并解包拼凑新列表 / 新字典规则把几个盒子里的东西全部倒出来放进一个新盒子里。底层机制CPython 会预计算总长度然后对每个元素执行一次插入字典则是哈希映射操作。时间复杂度O(M N)其中 M 和 N 分别是两个容器的长度。每解包一个元素底层就进行一次 C 级的内存拷贝memcpy或哈希插入。# 4.1 合并两个列表用 *list_a[1,2]list_b[3,4]combined_list[*list_a,*list_b,5,6]# 把两个列表和额外数字全拆开合并print(combined_list)# 输出: [1, 2, 3, 4, 5, 6]# 4.2 合并两个字典用 **dict_a{name:王五,age:25}dict_b{city:广州,job:工程师}merged_dict{**dict_a,**dict_b}# 把两本字典的内容抄进新字典print(merged_dict)# 输出: {name: 王五, age: 25, city: 广州, job: 工程师}# 4.3 如果两个字典有相同的键后面的会覆盖前面的d1{a:1,b:2}d2{b:999,c:3}print({**d1,**d2})# 输出: {a: 1, b: 999, c: 3} b被覆盖了致命的错误虽然解包是 O(N)但有一种情况极端危险切勿在函数参数中混合使用多次大规模解包例如func(*list1, *list2, *list3)在 Python 3.5 中这是合法的语法但底层并不会一次性合并再传参。它会依次遍历 list1压栈再遍历 list2压栈再遍历 list3压栈。如果这三个列表总大小为 1000 万虽然最终复杂度依然是 O(N)但传参过程会将所有参数全部压入栈帧Frame中。这会导致极高的内存占用r且传递大列表时解包速度远慢于直接传递列表本身。详细原因你要先理解 Python 函数调用时参数在底层是怎么存放的。直接传递列表func(list1)底层只做一件事把 list1 的“门牌号内存地址”压入栈帧。无论 list1 里有 1 个还是 1 亿个元素压进去的都只是一个地址占用 8 个字节。空间复杂度 O(1)常数级。解包传递func(*list1, *list2, *list3)底层必须做三件事CPython 源码 call.c 中的逻辑计算总长度算出 len(list1) len(list2) len(list3)。申请新内存在 C 层调用 PyTuple_New(total_length)创建一个能装下所有元素的巨型元组Tuple。拷贝指针用循环把 list1、list2、list3 里的每一个元素的指针逐个拷贝进这个新元组里。关键点虽然拷贝的是指针不是数据本身但 1000 万个指针占用 8字节 * 1000万 80 MB 内存。再加上元组本身的开销瞬间多出近百 MB 的额外消耗。更可怕的是这 80 MB 必须是一块连续的内存空间类似于数组如果内存碎片化申请失败直接报 MemoryError。解决方法场景推荐做法原因拼接多个小列表总长 1万放心用func(*a, *b, *c)优雅、简洁C 级速度极快损耗忽略不计。拼接超大列表总长 10万绝对禁止func(*a, *b, *c)内存翻倍 遍历拷贝性能雪崩。需要合并超大列表再处理改为func(a b c)列表拼接虽然也会新建列表但比解包压栈少一层元组转换稍好一点。极致的性能要求百万级直接传三个独立列表func(a, b, c)在函数内部用for循环遍历 a、b、c空间 O(1)时间仅遍历 1 次这是最优解。