10个解决复杂问题的Python工具

Python 有数千个库。

其中大多数解决非常具体的问题。

少数几个解决的问题似乎比它们所需的代码量大得多。

这些就是我一次又一次使用的工具。

不是因为它们很时髦。

而是因为它们悄悄地移除了数百行我宁愿永远不写的代码。

我特意避免了常见的推荐。在这里你找不到 requestsrichpandasFastAPI

如果你写了一段时间 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

汇智网翻译整理,转载请标明出处