10个解决复杂问题的Python工具
Python 有数千个库。
其中大多数解决非常具体的问题。
少数几个解决的问题似乎比它们所需的代码量大得多。
这些就是我一次又一次使用的工具。
不是因为它们很时髦。
而是因为它们悄悄地移除了数百行我宁愿永远不写的代码。
我特意避免了常见的推荐。在这里你找不到 requests、rich、pandas 或 FastAPI。
如果你写了一段时间 Python,你可能听说过这些库。
下面的这些值得更多关注。
1. graphlib — 无需编写图算法的依赖解析
Python 3.9 悄悄引入了 graphlib。
大多数开发者从未注意到。
如果你曾经构建过任务运行器、部署管道、插件加载器或构建系统,你可能至少编写过一次自己的依赖解析器。
你不再需要了。
from graphlib import TopologicalSorter
graph = {
"deploy": {"build"},
"build": {"test"},
"test": {"lint"},
"lint": set()
}
ts = TopologicalSorter(graph)
for task in ts.static_order():
print(task)
我在构建一个内部迁移框架时使用了这个框架,其中数据库更新相互依赖。在 graphlib 之前,我有近 200 行遍历逻辑。
一旦遇到循环依赖,局限性就显而易见了。TopologicalSorter 拒绝继续,这正是你想要的——但这也意味着你需要一个策略来检测并向用户解释这些循环。
经验丰富的开发者通常更喜欢使用这个而不是自定义 DFS 实现,因为维护变成了别人的问题。
2. contextlib.ExitStack — 动态资源管理
管理一个文件很容易。
管理二十个文件名直到运行时才知道的文件却出奇地尴尬。
这就是 ExitStack 的用武之地。
from contextlib import ExitStack
files = ["a.log", "b.log", "c.log"]
with ExitStack() as stack:
handles = [
stack.enter_context(open(f))
for f in files
]
for handle in handles:
print(handle.readline())
我第一次使用这个是在处理数百个轮转日志文件时。嵌套的 with 语句变得无法维护。
权衡是可读性。不熟悉 ExitStack 的开发者通常需要一分钟来理解发生了什么。
尽管如此,我宁愿解释一个不熟悉的抽象,也不愿调试泄漏的文件描述符。
3. sqlite3 — 藏在 Python 中的数据库
人们把 SQLite 当作玩具。
它不是。
对于本地索引、缓存、分析和开发工具,它通常是正确的选择。
import sqlite3
conn = sqlite3.connect("cache.db")
conn.execute("""
CREATE TABLE IF NOT EXISTS results (
key TEXT PRIMARY KEY,
value TEXT
)
""")
conn.execute(
"INSERT OR REPLACE INTO results VALUES (?, ?)",
("python", "cached response")
)
conn.commit()
我最喜欢的用途之一是替换每个月都变慢的巨大 JSON 缓存文件。
SQLite 几乎免费提供索引、事务和查询。
它的局限性出现在多写入器变得常见时。这通常是 PostgreSQL 更合适的时候。
4. tracemalloc — 无需猜测即可找到内存泄漏
内存泄漏很少会宣布自己。
它们只是让你的服务每天变慢一点。
tracemalloc 让 Python 显示内存实际来自哪里。
import tracemalloc
tracemalloc.start()
data = [bytearray(1024) for _ in range(10000)]
snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics("lineno")[:5]:
print(stat)
我希望我多年前就知道这个。
很长一段时间,每当内存使用量上升时,我都会责怪垃圾收集器。
通常,问题在于我的代码持有引用的时间比预期的要长。
局限性是运行时开销,所以除非我正在积极调查某事,否则我不会在生产中启用它。
5. shlex — 停止自己解析 Shell 命令
每个开发者最终都会写这个。
command.split()
然后有人传递:
python app.py "hello world"
一切都坏了。
shlex 已经解决了这个问题。
import shlex
command = 'python app.py --name "Jane Doe"'
parts = shlex.split(command)
print(parts)
我曾在部署工具、CLI 包装器和远程执行系统中使用过这个。
令人惊讶的是,有多少成熟的代码库仍然错误地解析命令字符串。
6. heapq — 不断查找前 N 个项目
仅仅为了找到最大的十个值而对整个集合进行排序通常是不必要的。
堆只保留重要的东西。
import heapq
largest = heapq.nlargest(
5,
[8, 19, 3, 50, 12, 40]
)
print(largest)
这在处理流式指标时变得无价,因为在内存中保留每个观察值是不现实的。
权衡是堆不是自然排序的。
期望排序列表的人通常会感到困惑,直到他们理解底层数据结构。
7. difflib — 远不止文本差异
大多数人将其与版本控制联系起来。
我用它进行模糊匹配。
from difflib import get_close_matches
commands = [
"deploy",
"rollback",
"restart",
"status"
]
print(get_close_matches(
"restar",
commands,
n=1
))
一个生产 CLI 使用这种方法自动更正拼写错误的命令。
用户认为我们构建了比实际更智能的东西。
它不是机器学习。
它只是一个设计良好的标准库模块。
8. watchdog — 停止轮询文件系统
每秒轮询一次感觉很简单。
直到你的笔记本电脑风扇提醒你并非如此。
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class Handler(FileSystemEventHandler):
def on_modified(self, event):
print(event.src_path)
observer = Observer()
observer.schedule(
Handler(),
".",
recursive=True
)
observer.start()
我现在几乎在所有可用的地方使用文件系统事件。
局限性是特定于平台的行为。Linux、Windows 和 macOS 在底层暴露不同的文件系统通知,因此边缘情况仍然存在。
即便如此,事件驱动的监控几乎总是胜过定期扫描。
9. cachetools — 超越 functools.lru_cache 的缓存
lru_cache 非常出色。
直到你需要过期。
或大小限制。
或自定义驱逐策略。
from cachetools import TTLCache
cache = TTLCache(
maxsize=100,
ttl=300
)
cache["config"] = {"debug": False}
我曾将此用于 API 响应,其中五分钟后数据过期是完全可以接受的。
我学到的一个微妙教训:缓存过期既是产品决策,也是技术决策。
没有库能为你回答这个问题。
10. py-spy — 无需重启程序即可分析
这个工具感觉像作弊。
它可以检查正在运行的 Python 进程,而无需修改你的应用程序。
py-spy top --pid 4821
或生成交互式火焰图。
py-spy record --pid 4821
我第一次使用它时,我发现一个线程空闲,而另一个线程几乎将所有时间都花在编译正则表达式上。
我已经优化了错误的函数两天了。
它最大的局限性是它告诉你哪里花了时间,但不一定是为什么。解释仍然需要经验。
但与在生产服务中散布计时器相比,它提供了更诚实的图像。
原文链接:10 Python Tools That Make Complex Problems Feel Surprisingly Simple
汇智网翻译整理,转载请标明出处