zip生成元组的访问方式及排序后索引存储技术疑问
首先来说第一个问题:为什么把zip结果转为列表后能拿到内部元组,而生成器表达式不行?
Python 3里的zip()返回的是迭代器,不是列表。迭代器的特性是只能被遍历一次,而且不能直接用索引(比如zip_obj[0])访问元素。当你用list(zip(...))的时候,list()会把迭代器里的所有元素一次性提取出来,转换成列表的元素,这时候你就能用[0]拿到第一个元组了。
而你写的生成器表达式(i for i, v in zip(*sorted(...))),本质上只是创建了一个生成器对象——生成器是惰性求值的,只有当你主动遍历它的时候才会去执行逻辑。集合会直接把这个生成器对象存进去,而不会帮你遍历它拿到结果。另外这里还有个逻辑问题:zip(*sorted(...))的每个元素是长度为6的元组,你试图把它解包成i和v两个变量,实际遍历的时候会直接抛出ValueError(6个元素没法分给两个变量),只是因为生成器没被执行,所以你没看到报错。
接下来是第二个问题:有没有无需转列表就能获取zip生成的目标元组的替代方法?
当然有,给你几个更高效的方案:
用
next()直接取第一个元素:
因为zip(*sorted(...))的迭代器里第一个元素就是你要的索引元组,直接用next()提取即可,不用把所有元素都转成列表:my_set.add(next(zip(*sorted(enumerate(l), key=lambda x: x[1], reverse=True))))这种方式只取第一个元素,剩下的迭代器内容会被丢弃,数据量大的时候比转列表更高效。
转成元组再取索引:
用tuple()替代list(),效果类似但元组是不可变类型,更轻量一点:my_set.add(tuple(zip(*sorted(enumerate(l), key=lambda x: x[1], reverse=True)))[0])直接从排序结果提取索引(更直观):
其实可以跳过zip,直接从排序后的(索引, 值)元组里提取索引,再转成元组,可读性更好:my_set.add(tuple(idx for idx, val in sorted(enumerate(l), key=lambda x: x[1], reverse=True)))
最后说说zip生成的元组的不同访问方式:
因为zip()返回的是迭代器,访问它的元素主要有这几种方式:
转成容器类型(列表/元组):用
list(zip_obj)或tuple(zip_obj)把迭代器所有元素一次性提取出来,之后可以用索引访问(比如result[0]),或者反复遍历。但注意,迭代器被取空后就不能再使用了。用
next()逐个提取:每次调用next(zip_obj)会取出下一个元组,直到迭代器耗尽抛出StopIteration。适合只需要前几个元素的场景,比如只取第一个元素时非常高效。遍历迭代器:用
for循环逐个处理每个元组,比如:for item in zip(*sorted_list): print(item)解构赋值:如果知道
zip返回的元素数量,可以直接解构到变量里,比如你同时需要索引和值的话:indices, values = zip(*sorted(enumerate(l), key=lambda x: x[1], reverse=True))这样
indices就是你要的索引元组,values是对应的值元组,非常方便。
内容的提问来源于stack exchange,提问作者plx

