ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python高级开发必备的9大数据结构

Python高级开发必备的9大数据结构 请你停止使用 list 来折腾性能方面的问题。下面介绍了 9 个 隐藏起来的大杀器, 它们能够让你的代码在一瞬间变得非常专业起来。很多人学习的时候, 把情况停在了列表和字典, 觉得这样已经足够用了。说句实话, 我也曾经那样不明智地相信普通数据结构能够解决所有问题, 直到项目处于高频并发之下出现崩溃, 才不得不回头去重新使用标准库的这些传统工具。咱们先聊聊我身边发生的一件真事, 我那位名叫小张的朋友负责搞日志收集这一摊子事儿, 那时候他用的队列是个叫作 list 的列表变量, 等到使用那个程序的人稍微多一点的时候, 每次执行 pop(0) 这个操作的速度简直就慢得像蜗牛爬一样, 结果造成的延迟时间从最开始不过是几十毫秒的小数字一下子飙升到了几百毫秒那么老高的数字, 后来他把底下用的那种结构给换成了 .deque 这种新花样, 那一刻系统的吞吐能力一下子就恢复回来了, 而且延迟时间也变得稳稳当当不再乱跳了, 这么一弄之后我就挺明白了, 觉着好多所谓的性能瓶颈压根不是什么高深的算法难题, 其实就是因为在选数据结构的时候选错了路。有些工具看起来好像是语法糖, 但是实际上它们能够省掉非常多的重复代码。就像是那个功能, 它省去了需要手动判断键是否在字典里面的样板式判断逻辑, 这让代码变得更加简洁了, 也更容易保证初始化逻辑不会被忘记。还有另外一个例子, 也就是用于统计频次的那个东西, 只用一行代码就能把活儿办完了, 这样就省掉了手写循环计数的过程, 同时也降低了出错的可能性。说实在的大白话就是, 这些结构并不是为了展示什么花哨的技术, 而是把那些常见的坑和陷阱都给封装好了, 让你可以直接拿过来用就是了。关于可读性和维护性这两方面, 它确实是一个相当不错的朋友。普通的元组会让人们被迫通过索引去猜测其中的含义, 当代码阅读的时间变长以后, 这种状况会让人生出面对魔法数字时的痛苦感受。使用特定方法为字段命名, 可以在不牺牲元组的轻量级性能这一前提之下, 把代码的可读性提升非常多。在实际的项目开发过程之中, 我曾经使用这种方法把数据库所返回的行数据封装成了能够直接通过属性进行访问的对象, 这样一来, 团队里面的新员工在上手工作的速度上就有了非常明显的加快现象。在进行优先级的处理以及调度的相关工作过程里, 不少人会产生一种下意识反应, 他们往往选择把整个列表内容进行排序操作, 然后再从里面取出最小值, 这种方法在实际应用当中是非常浪费时间与效率的。heapq 这个工具提供了专门针对堆的操作功能, 它能够维护一个小顶堆数据结构, 在这样的结构下, 插入新的元素或者弹出现有的元素, 其所需的时间复杂度都是对数级别的时间, 因此它非常适合被用于构建任务优先级队列, 或者是用来解决需要找到前 k 个最大或最小值的那一类 top-k 问题。但是有一点是需要特别注意到的, 即 heapq 默认实现的是最小堆机制, 如果用户想要把它当作最大堆来使用的话, 就可以通过将数值取负数的这种处理方式来实现目的。还有一个常常出现的误会就是, 以为能保证插入操作是对数时间的复杂度。实际上呢?查找目标位置所用的时间确实只是 O(log n)。可是, 要把元素真正插进列表里头的话, 仍然不可避免需要挪动后面的其他元素。这样一来, 整体的总耗时就变成了 O(n) 了。把这部分细节弄明白了, 能够协助你在考量性能与复杂度的时候, 作出更为明智的决策。当数据的量变得非常大的时候, 内存方面会成为首先碰到的, 也就是第一个天花板。使用普通的列表去存储大量的数字, 这个做法不仅十分占用内存资源, 而且处理起来的效率非常慢。而在使用了array这一模块的情况下, 并不需要去增加任何第三方的库, 就能把序列中的那些数字所占用的内存消耗明显地降低下来。我自己曾经进行过一次尝试性的操作, 是把那种以百万数量为级别的整数数据, 从原本所用的列表格式转换成了array格式。经过这样子的改动之后发现, 其占用的内存空间差不多变成了一开始的一半, 同时那个叫做GC的压力也得到了大幅度的减轻。但这里是需要给大家一个提醒的, 就是 array 这个功能其实它的范围是有一定局限的, 那么在具体去做数值计算方面的事情的时候, 大家还是要优先去选择 numpy 这个工具而如果说我们的使用场景是属于那种轻量化的并且对于内存大小非常敏感的那种情况, 那么这时候就应该去考虑使用标准库了。还有一些微妙但实用的东西像 的在实现简单的 LRU 缓存时特别方便。虽然 .7 之后 dict 保持插入顺序但 依然有方法上的优势用来手动调整元素顺序或实现淘汰策略时更直观。再谈集合时set 的平均查找是 O(1)比 list 快得多当你需要不可变的集合做字典键时 就派上用场。我们务必要对可变对象同不可变对象二者的含义给予充分的理解, 因为在日常的工作里, 如果把可变对象当作键名来使用的话, 很容易就会引发一些逻辑上的错误, 所以理解这一点是非常必要的。谈到应当使用标准库还是选用第三方组件这个话题, 那是非常容易导致争论的。我个人的倾向性在于, 只要能够借助标准库来解决的问题, 都会优先去使用这些内容, 背后的理由其实是非常直接的, 那就是部署的操作非常简单、依赖关系也很少、系统的稳定程度会比较高一些。可是, 当遇到复杂的数值计算任务或者是需要进行大规模数据分析工作的时候, 那些诸如像numpy这样的第三方工具, 才是完全无法被其他东西所替代的高效利器。这个问题的关键之处, 就在于要把不同的应用场景给区分清楚, 要弄明白在什么样的情况下该用什么样的库。标准库这种类型的东西, 更加地适合那种工程层面的、比较轻量的优化操作, 同时它还能保证系统的可靠性。而第三方库呢, 它们更擅长的领域, 是在那些专门的、特定的方面来实现性能的提升以及功能的扩展。如果你真想把这些工具变成武器, 我建议你先去做一次代码审计。你要找出那些经常用 pop(0) 的地方。你要找出那些进行全表排序然后取最小值的地方。你要找出那些手写二分查找的地方。你要找出那些手动计数或者是存储海量基础数值的地方。找到这些地方之后, 你再有针对性地去做替换。你可以把那些地方替换成 deque。你可以把那些地方替换成 heapq, 但要注意的是它是有插入代价的。你也可以把那些地方替换成 array。在实际操作的时候, 切记要采用那些简单直观的基准测试法子, 把改造之前和改造之后在内存占用以及时间成本这两个方面的情况给对照一下, 只有这样, 才能够让你的团队心甘情愿地去接受并采纳你提出的这些改动方案, 与此同时, 也能够有效避免那种没有章法的、盲目的优化行为最后导致的一系列负面结果或者不良后果。坦白讲, 笔者个人以为, 倘若你能够把标准库视作一种工具箱的工具集合, 而不是将其视作日常例行公事的机械操作, 那么你便能够在工程实践环节里面减少诸多不必要的弯路走向, 当然, 这些库组件并不会确保在任何情况之下都能带来令人瞠目结舌的巨大性能提升结果, 不过, 在处理最关键执行路径的场景之内, 它们却往往具备能够有效封堵常见代码缺陷陷阱的功能能力, 反正, 我是持如此看法的, 编写相关代码程序的时候绝不要仅仅单凭直觉进行判断行为, 应该先去自我提问一下: 这是否属于高频率的操作类型?数据量的规模是否会持续呈现增长的态势? 系统运行是否需要严格保证逻辑顺序亦或是需要维持状态不可变更性?这些问题答案所得出的结果将会帮助你做出决策动作, 从而决定究竟该拿出哪一件所谓的厉害工具来进行使用处理操作。请问在您的项目开发过程之中, 究竟什么样的情况构成了您最为头疼的性能阻滞因素呢? 此外, 是否存在那么一次经历, 是因为对某个标准库里面所具备的数据结构进行了替换操作, 从而使得问题得到了根本性的解决?希望您能详细描述一下您所经历的这种故事内容以及其背后蕴含的具体技术细节以便让所有的旁观者都能够借此机会学习到如何避免仅仅凭借直觉去实施那种对于代码的改造行为。
返回列表