
Show HN: A modern reimagining of the Python progresssss bar
A modern reimagining of the Python progresssss bar.
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
A modern reimagining of the Python progresssss bar.
psnek not only supports the expected features of progress bars such as nesting and writing, but it also supports adding more metrics and logs to easily see how important data changes as the progress bar...progresses.
psnek.mp4
Additionally, the bars are extremely customizable, from changing the gradient color, to changing whether text is printed above or below the progress bar, to changing the height of the progress bar to fit even more metrics!
psnek_tall.mp4
More useful features of psnek:
The notebooks folder contains many detailed and interactive Jupyter Notebook examples which show how psnek works!
psnek can be installed from PyPI (Python 3.11+):
pip3 install psnek
uv pip install psnek
Demo
Wrap any iterable with track() and optionally give it a description.
import psnek
total = 0 numbers = range(1, 121) for number in psnek.track(numbers): total += number
print(total)
For anything else, you can update a Bar manually, from any thread. Use with so the bar is always closed and so an Exception marks it as failed:
options = psnek.Options(unit="B", scale="iec")
with psnek.Bar(total=size, desc="upload", options=options) as bar: for chunk in chunks: send(chunk) bar.update(len(chunk))
You can add status fields after the metrics with status(), either as keywords or as a dict whose keys don't need to be valid Python names. Setting a field to None removes it.
bar.status(loss=0.137, lr=3e-4) bar.status({"val loss": 0.142, "lr": None})
Bars started inside another bar's loop are drawn as a tree below it. By default, only the top-level bar's final line is kept once it finishes.
for epoch in psnek.track(3, "epochs"): for batch in psnek.track(batches, f"epoch {epoch + 1}"): ...
⣄ epochs ⣿⣿⣿⣿⣿⣿⣿⣿⡆⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 33.3% 1/3 0:21<0:22 11.1s/it └─⠋ epoch 2 ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 41.2% 412/1000 0:09<0:12 45.8it/s └─⢰ eval ⣿⣿⣿⣿⣿⣿⡏⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 26.6% 17/64 0:00<0:01 41.0it/s
To print a line above whatever bars are live:
psnek.write("finished shard", 3)
Jupyter Notebook with a tour of everything above.
Each cell of the bar is an 8-dot Braille character that fills in eight steps, so consecutive cells read as one winding coil: ⠀ ⡀ ⡄ ⡆ ⡇ ⡏ ⡟ ⡿ ⣿. When the total is unknown, the snake slithers back and forth, led by a face that reflects the run's mood. If progress stops for an unusually long time for that loop, the snake slows down and the bar turns amber (stalled 12s), then red (stuck 48s).
While a bar is live in a terminal, anything you print() or log is routed into a panel drawn below the bars instead of breaking them. You can also write to the panel directly:
bar.log("processed", path) # append a message to the panel, like logging bar.log("loss spiked", level=logging.WARNING) # with a logging level bar.panel("worker 1: idle\nworker 2: busy") # replace the panel's content bar.panel(None) # clear it bar.write("saved model") # print permanently above the bars, like print()
log() is for messages about the run: each one starts with its level (debug, info, warn, error, or fatal), and rises in salience with it, from a faint debug line to bold warnings and errors in the bar's own colors. Options(log_time=True) starts each message with the system time. Logged messages stay indented under the bars once the panel pushes them above, while write() prints its text as-is, flush left:
16:30:33 info batch 10 ingested checkpoint saved: ingest-015.parquet ⢰ ingest ⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣀ 90.0% 27/30 0:00<0:00 38.5it/s rows=27000 ⡇ 16:30:33 warn batch 18: 3 malformed rows skipped ⡇ 16:30:33 error upload of batch 24 failed ⡇ retrying in 1s
panel() also lays out structured data as a borderless table: a dict, dataclass, or named tuple becomes bold keys beside their values, while a list of them (or a polars or pandas DataFrame) becomes rows under a bold header.
bar.panel({"loss": 0.13761, "learning_rate": 3e-4, "epoch": 3}) bar.panel([{"model": "llama", "acc": 0.912}, {"model": "gpt2", "acc": 0.6, "note": "base"}])
⡇ loss 0.1376 ⡇ learning_rate 0.0003000 ⡇ epoch 3
⡇ model acc note ⡇ llama 0.9120 ⡇ gpt2 0.6000 base
Jupyter Notebook
Without a terminal (CI, pipes, and AI coding agents such as Claude Code or Codex), psnek prints plain, newline-terminated status lines with no escape codes: a start line, one every 10 seconds, one whenever a bar stalls, gets stuck, or recovers, and a final summary.
ingest: 0.0% 0/100 0:00<--:-- --it/s ingest: 50.0% 50/100 0:06<~0:06 stalled 5s 40.6it/s ingest: 53.0% 53/100 0:07<~0:06 7.3it/s (recovered) ingest: 100.0% 100/100 0:08 done 13.9it/s
psnek detects common CI and agent environment variables automatically. (Jupyter Notebook)
pmap() maps a function over items with progress and returns the results in input order.
results = psnek.pmap(fetch, urls, "fetch", workers=16)
For asyncio, atrack() wraps an async iterable, gather() works like asyncio.gather(), and amap() works like pmap() with at most workers calls running at once. Both gather() and amap() count each call as it completes, and cancel the remaining calls if one fails.
async for message in psnek.atrack(stream, "messages"): ...
pages = await psnek.gather(*(fetch_page(url) for url in urls), desc="fetch") pages = await psnek.amap(fetch_page, urls, "fetch", workers=16)
Jupyter Notebook
On a free-threaded build of Python such as python3.14t, CPU-bound pmap() on plain threads scales across cores and update() stays cheap even when many threads call it at once. (Jupyter Notebook)
psnek.Options holds the presentation settings, and can be shared by any number of bars. (Jupyter Notebook)
psnek was mostly made using Claude Opus 5.5 with checks and balances, both a) as an experiment into seeing how well its design sense can translate into the terminal domain and b) the idea to use Braille subcharacters as a snake for a progress bar came to me after a glass of wine and I wanted to see how well Opus 5.5 could execute it. Opus 5.5 was successful, although it required significant back and forth on the design and a substantial amount of user testing (for example, to catch unexpected visual layout/reflow changes that happen too quickly for agents to automatically catch).
I have reviewed all the code and documentation copy manually in this repository and I stand by it professionally. For any pull requests that are submitted to this repo (particularly by agents), please manually review the output and ensure any changes are high signal and precisely scoped: low-effort PRs that cause more problems than they solve may be closed without comment.
The terminal GIFs/videos were made using vhs, which apparently Opus 5.5 can use well. An additional postprocessing step was added to crop the resulting video to only salient dimensions in order to cut down on unnecessary space and reduce file size. The demo files and vhs tapes are available in the demo folder.
The default gradient colors were chosen to fit two specific criteria: a) sufficient color contrast for both dark and light terminal themes and b) colorblind friendliness against other semantic bar colors. The palette is similar to viridis sans the yellows which don't play well with light backgrounds.
A bar is considered stalled once the wait since its last progress is past the smoothed mean of its gaps plus 3 standard deviations (and at least 5 seconds), and stuck past 9 standard deviations (and at least 30 seconds). The first 3 gaps are never flagged, so a slow first step such as loading a model is fine.
The ETA drops its ~ only when psnek is 99% confident it is within 10% (or a second). During warm-up, irregular loops, changes in pace, and stalls, it keeps the ~.
All text psnek draws is sanitized, so captured output can't move the cursor or spoof the display.
To Be Added
CLI support for non-Python pipes (implemented but not tested)
Canonical psnek callbacks for all major Python machine learning frameworks (e.g. Hugging Face transformers, Keras)
Maintainer/Creator
Max Woolf (@minimaxir)
Max's open-source projects are supported by his Patreon and GitHub Sponsors. If you found this project helpful, any monetary contributions to the Patreon are appreciated and will be put to good creative use.
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。