23 Sep 2026
Planet Python
Python GUIs: Dynamically Adding Rows of Widgets in PyQt6 — How to use QGridLayout to add multiple widgets per row at runtime
I have a GUI where I dynamically add a QLineEdit for each image a user selects. That part works. But now I want to extend it so that each image gets a row of three text boxes - one for the filename, one for the width, and one for the height. How do I dynamically add multiple widgets horizontally for each new entry?
Adding a single widget per action is straightforward enough, but when you need a whole row of widgets each time? That's where QGridLayout becomes your best friend.
In this tutorial, we'll walk through how to dynamically add rows of QLineEdit widgets using QGridLayout in PyQt6. Each time the user selects images, a new row appears with three text boxes: the filename, image width, and image height. We'll also look at how you can use a custom widget to group related fields together.
Why QGridLayout?
If you've been using QVBoxLayout to stack widgets vertically, you've probably noticed that it only gives you one column. To place multiple widgets side by side and keep adding new rows, you need a two-dimensional layout. QGridLayout lets you place widgets by specifying a row and column, which makes it perfect for building table-like arrangements dynamically.
layout.addWidget(some_widget, row, column)
Each time you add a new file, you increment the row number and place your three widgets at columns 0, 1, and 2.
Setting Up the Window
Let's start with a basic main window that has a button to open a file dialog and a grid layout ready to receive our dynamic rows.
import sys
import os
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QPushButton,
QGridLayout, QLineEdit, QFileDialog, QLabel,
QVBoxLayout,
)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Image Selector")
# Central widget and outer layout
central_widget = QWidget()
self.setCentralWidget(central_widget)
outer_layout = QVBoxLayout()
central_widget.setLayout(outer_layout)
# Button to open file dialog
self.button_open = QPushButton("Open Images")
self.button_open.clicked.connect(self.load_images)
outer_layout.addWidget(self.button_open)
# Header labels
header_layout = QGridLayout()
header_layout.addWidget(QLabel("Filename"), 0, 0)
header_layout.addWidget(QLabel("Width"), 0, 1)
header_layout.addWidget(QLabel("Height"), 0, 2)
outer_layout.addLayout(header_layout)
# Grid layout for dynamic rows
self.grid_layout = QGridLayout()
outer_layout.addLayout(self.grid_layout)
# Stretch at the bottom to push everything up
outer_layout.addStretch()
# Track how many rows we've added
self.current_row = 0
def load_images(self):
paths, _ = QFileDialog.getOpenFileNames(
self, "Select Images", "", "Images (*.png *.jpg *.bmp);;All Files (*)"
)
for path in paths:
filename = os.path.basename(path)
width, height = self.get_image_size(path)
self.add_image_row(filename, width, height)
def get_image_size(self, path):
# Placeholder - replace with your own logic
return 0, 0
def add_image_row(self, filename, width, height):
line_edit_name = QLineEdit(filename)
line_edit_width = QLineEdit(str(width))
line_edit_height = QLineEdit(str(height))
self.grid_layout.addWidget(line_edit_name, self.current_row, 0)
self.grid_layout.addWidget(line_edit_width, self.current_row, 1)
self.grid_layout.addWidget(line_edit_height, self.current_row, 2)
self.current_row += 1
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Run this and click "Open Images." Each file you select gets its own row with three editable text boxes. Click the button again to add more.
How It Works
The add_image_row method is where the dynamic creation happens. Each time it's called, it:
- Creates three
QLineEditwidgets - one for the filename, one for width, one for height. - Adds them to the grid layout at the current row, in columns 0, 1, and 2.
- Increments
self.current_rowso the next call places widgets on the next row.
Because QGridLayout handles positioning for you, you don't need to manually manage spacing or alignment. The widgets line up in neat columns automatically.
Extracting Real Image Dimensions
The get_image_size method above is a placeholder. If you want to extract actual image dimensions, you can use QImageReader from PyQt6 itself:
from PyQt6.QtGui import QImageReader
def get_image_size(self, path):
reader = QImageReader(path)
size = reader.size()
if size.isValid():
return size.width(), size.height()
return 0, 0
This reads only the image header, so it's fast even for large files.
Adding a Computed Column
Since the original question mentioned calculating the area from width and height, let's add a fourth column that shows the computed area. We can make this column read-only so users don't accidentally edit it.
def add_image_row(self, filename, width, height):
line_edit_name = QLineEdit(filename)
line_edit_width = QLineEdit(str(width))
line_edit_height = QLineEdit(str(height))
line_edit_area = QLineEdit(str(width * height))
line_edit_area.setReadOnly(True)
self.grid_layout.addWidget(line_edit_name, self.current_row, 0)
self.grid_layout.addWidget(line_edit_width, self.current_row, 1)
self.grid_layout.addWidget(line_edit_height, self.current_row, 2)
self.grid_layout.addWidget(line_edit_area, self.current_row, 3)
self.current_row += 1
You'd also want to add a matching "Area" header label in the __init__ method.
Encapsulating a Row as a Class
As your rows get more complex, it helps to group the widgets for each row into their own class. This keeps MainWindow clean and makes it easy to access or update individual rows later.
class ImageInfoRow:
def __init__(self, filename, width, height, grid_layout, row):
self.line_edit_name = QLineEdit(filename)
self.line_edit_width = QLineEdit(str(width))
self.line_edit_height = QLineEdit(str(height))
self.line_edit_area = QLineEdit(str(width * height))
self.line_edit_area.setReadOnly(True)
grid_layout.addWidget(self.line_edit_name, row, 0)
grid_layout.addWidget(self.line_edit_width, row, 1)
grid_layout.addWidget(self.line_edit_height, row, 2)
grid_layout.addWidget(self.line_edit_area, row, 3)
Then in your main window, creating a row becomes a single line:
row = ImageInfoRow(filename, width, height, self.grid_layout, self.current_row)
self.image_rows.append(row)
self.current_row += 1
Storing each ImageInfoRow in a list (self.image_rows) lets you access or modify any row later - for example, to read back edited values or to remove a row.
Alternative Approaches
QGridLayout is a great fit here, but there are other ways to achieve similar results:
- QVBoxLayout with QHBoxLayout rows - Make each row a
QWidgetwith its ownQHBoxLayoutcontaining three line edits, then add each row widget to a vertical layout. This gives you self-contained row widgets that are easy to add and remove. - QTableView with a model - If you're dealing with many images or want features like sorting and filtering, a
QTableViewbacked by aQStandardItemModel(or a custom model) is a more scalable approach. See the PyQt6 QTableView tutorial for details.
For a handful of images, the grid layout approach is simple and works well. For dozens or hundreds, consider switching to a model/view approach.
Complete Working Example
Here's the full example with real image size extraction and a computed area column:
import sys
import os
from PyQt6.QtGui import QImageReader
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QPushButton,
QGridLayout, QLineEdit, QFileDialog, QLabel,
QVBoxLayout,
)
class ImageInfoRow:
"""Holds a row of QLineEdits for one image."""
def __init__(self, filename, width, height, grid_layout, row):
self.line_edit_name = QLineEdit(filename)
self.line_edit_width = QLineEdit(str(width))
self.line_edit_height = QLineEdit(str(height))
self.line_edit_area = QLineEdit(str(width * height))
self.line_edit_area.setReadOnly(True)
grid_layout.addWidget(self.line_edit_name, row, 0)
grid_layout.addWidget(self.line_edit_width, row, 1)
grid_layout.addWidget(self.line_edit_height, row, 2)
grid_layout.addWidget(self.line_edit_area, row, 3)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Image Selector")
self.setMinimumWidth(600)
# Central widget and outer layout
central_widget = QWidget()
self.setCentralWidget(central_widget)
outer_layout = QVBoxLayout()
central_widget.setLayout(outer_layout)
# Button to open file dialog
self.button_open = QPushButton("Open Images")
self.button_open.clicked.connect(self.load_images)
outer_layout.addWidget(self.button_open)
# Header labels
header_layout = QGridLayout()
header_layout.addWidget(QLabel("Filename"), 0, 0)
header_layout.addWidget(QLabel("Width"), 0, 1)
header_layout.addWidget(QLabel("Height"), 0, 2)
header_layout.addWidget(QLabel("Area"), 0, 3)
outer_layout.addLayout(header_layout)
# Grid layout for dynamic rows
self.grid_layout = QGridLayout()
outer_layout.addLayout(self.grid_layout)
# Push rows to the top
outer_layout.addStretch()
# Track rows
self.current_row = 0
self.image_rows = []
def load_images(self):
paths, _ = QFileDialog.getOpenFileNames(
self,
"Select Images",
"",
"Images (*.png *.jpg *.bmp);;All Files (*)",
)
for path in paths:
filename = os.path.basename(path)
width, height = self.get_image_size(path)
row = ImageInfoRow(
filename, width, height,
self.grid_layout, self.current_row,
)
self.image_rows.append(row)
self.current_row += 1
def get_image_size(self, path):
reader = QImageReader(path)
size = reader.size()
if size.isValid():
return size.width(), size.height()
return 0, 0
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Click "Open Images," select a few image files, and you'll see a row appear for each one with the filename, dimensions, and computed area. Click the button again to add more images - each new selection appends to the existing list.
From here you could add scroll support (wrap the grid in a QScrollArea), add a "Remove" button per row, or switch to a QTableView if your needs grow. The core pattern - tracking a row counter and placing widgets into a QGridLayout - stays the same regardless of how you extend it.
For an in-depth guide to building Python GUIs with PyQt6 see my book, Create GUI Applications with Python & Qt6.
23 Sep 2026 6:00am GMT
Python Bytes: #497 Faster than light profiling
<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://docs.python.org/3.15/library/profiling.sampling.html?featured_on=pythonbytes">Tachyon: A sampling profiler ships in Python 3.15's stdlib</a></strong></li> <li><strong><a href="https://blog.cloudflare.com/python-workers-ga/?featured_on=pythonbytes">Python Workers are now generally available on Cloudflare</a></strong></li> <li><strong><a href="https://flet.dev/blog/flet-1-0?featured_on=pythonbytes">Flet 1.0 - build cross-platform apps in Python</a></strong></li> <li><strong><a href="https://github.com/ljchang/marimo-book?featured_on=pythonbytes">marimo-book: Build static books from marimo notebooks</a></strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=__iKh7BNDgM' style='font-weight: bold;' data-umami-event="Livestream-Past" data-umami-event-episode="497">Watch on YouTube</a><br> <p>Sponsored by <strong>Logfire from Pydantic</strong>: <a href="https://pythonbytes.fm/logfire">pythonbytes.fm/logfire</a> <strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href="https://fosstodon.org/@mkennedy">Mastodon</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/mkennedy?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/mkennedy/?featured_on=pythonbytes">LinkedIn</a></li> <li>Calvin: <a href="https://sixfeetup.social/@calvin?featured_on=pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/calvinhp.com?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/calvinhp?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/calvinhp/?featured_on=pythonbytes">LinkedIn</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/pythonbytes.fm">BlueSky</a> / <a href="https://x.com/PythonBytes?featured_on=pythonbytes">X</a></li> </ul> <p>Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually <strong>Tuesday at 7am PT</strong>. Older video versions available there too.</p> <p>Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it.</p> <p><strong>Michael #1: <a href="https://docs.python.org/3.15/library/profiling.sampling.html?featured_on=pythonbytes">Tachyon: A sampling profiler ships in Python 3.15's stdlib</a></strong></p> <ul> <li>Python 3.15 adds the <code>profiling</code> package per PEP 799: <code>profiling.tracing</code> (where cProfile moved) and <code>profiling.sampling</code>, the new sampler called Tachyon</li> <li>py-spy and Austin exist but copy raw interpreter bytes with no API, so every CPython release risks breaking them; one in the stdlib is a contract to stop breaking profilers</li> <li>Defaults: 1 kHz, main thread, wall clock, and a <code>-live</code> top-like view for poking at a slow server</li> <li>Output is flexible: pstats, <code>-flamegraph</code>, <code>-diff-flamegraph</code> against a baseline, <code>-heatmap</code> on source lines, <code>-opcodes</code> for specialized bytecode, <code>-gecko</code> for Firefox Profiler with GIL and GC markers</li> <li>Profiling modes: wall, cpu, gil (which function is starving my other threads?), and exception, plus <code>-async-aware</code> to see the task graph instead of just <code>select()</code>, <code>-all-threads</code>, and <code>-subprocesses</code> forking a profiler per child</li> <li>Near-zero overhead for production; guidance is 10-30 second windows on representative load, and free-threaded builds divide the rate by thread count</li> <li>Attach to a running PID, same minor version only; ptrace permissions are the main friction. A 3.14 backport already exists on GitHub</li> <li>Caveat: it only sees Python frames, so 90% in <code>calculate()</code> hides NumPy underneath. For native stacks there's Cronon from HRT, 200k samples/sec over DWARF, not yet open source</li> </ul> <p><strong>Calvin #2: <a href="https://blog.cloudflare.com/python-workers-ga/?featured_on=pythonbytes">Python Workers are now generally available on Cloudflare</a></strong></p> <ul> <li>Python Workers are out of beta - now GA, "first-class" language on Cloudflare's Developer Platform</li> <li>No more manual JS interop: bindings (queues, R2, D1, Durable Objects) now work natively in Python, e.g. self.env.QUEUE.send({...})</li> <li>Runs on Pyodide (WASM-compiled Python), with real TCP socket support for DB connectivity</li> <li>Frameworks supported: FastAPI, Django, Flask; AI libs like OpenAI SDK, LangChain, MCP</li> <li>Underlying platform work formalized as PEP 783 (PyEmscripten), after a year of discussion</li> <li>Bottom line: write real Python on Cloudflare's edge, no JS glue code required</li> </ul> <p><strong>Calvin #3: <a href="https://flet.dev/blog/flet-1-0?featured_on=pythonbytes">Flet 1.0 - build cross-platform apps in Python</a></strong></p> <ul> <li>Flet hits 1.0 - build Flutter-backed apps from pure Python, no frontend experience needed</li> <li>One codebase targets six platforms: iOS, Android, Windows, macOS, Linux, web</li> <li>150+ built-in UI controls, plus support for custom controls / wrapping Flutter packages</li> <li>Mobile now supports real Python packages: NumPy, pandas, Pillow, cryptography</li> <li>Comes with pytest-based UI testing and an MCP integration for AI coding assistants</li> <li>Milestone lands 4+ years after its first PyPI release (Sept 2022) - signals "production ready," not experimental</li> </ul> <p><strong>Michael #4:</strong> <a href="https://github.com/ljchang/marimo-book?featured_on=pythonbytes">marimo-book: Build static books from marimo notebooks</a></p> <p>marimo-book is a Jupyter-Book-style static site generator built specifically for marimo .py notebooks. It ships polished multi-page sites with Material for MkDocs theming, full-text search, dark mode, and code copy, plus a content-hashed incremental build cache that drops rebuilds from 100+ seconds to roughly 3 seconds on real books. Standout extras include anywidget rendering without a kernel, static reactivity for discrete sliders via pre-rendered lookup tables, an opt-in WASM/Pyodide mode per chapter, and per-chapter launch buttons.</p> <ul> <li>If you've wanted to publish a marimo notebook as a real book or course site without hosting a kernel, marimo-book gives you the static, searchable, fast-loading output you'd expect from Jupyter Book.</li> <li><strong>Alpha (0.1.x), but in production:</strong> pin marimo-book>=0.1.5,<0.2; the book.yml schema is stable for v0.1, and <a href="http://dartbrains.org/?featured_on=pythonbytes">dartbrains.org</a> is a real-world user.</li> <li><strong>Two-stage build by design:</strong> a marimo-aware preprocessor emits plain Markdown + inline HTML, then mkdocs (Material today, zensical tomorrow) renders it. Not a mkdocs plugin, so the shell stays swappable.</li> <li><strong>Interactive widgets without a kernel:</strong> anywidget Canvas/Three.js/Plotly mounts render statically, and mo.ui.slider with explicit steps gets pre-computed as a static lookup table.</li> <li><strong>WASM escape hatch per chapter:</strong> set mode: wasm and the chapter routes through marimo's MarimoIslandGenerator, shipping the marimo runtime + Pyodide bundle for full reactivity where you need it.</li> <li><strong>Per-chapter launch buttons and extras:</strong> readers can jump to molab, GitHub, or a downloaded .py; optional [social], [linkcheck], and [pdf] extras cover OG cards, htmlproofer, and WeasyPrint PDF export.</li> <li><strong>Sandboxed notebooks:</strong> the sandbox mode reads PEP 723 inline metadata and provisions per-notebook envs via uv for portable builds, at the cost of slower first runs.</li> </ul> <p><strong>Extras</strong></p> <p><strong>Calvin</strong>:</p> <ul> <li>Great overview of a new feature in Python 3.15 - <a href="https://realpython.com/python315-frozendict/?featured_on=pythonbytes">frozendict</a></li> </ul> <p><strong>Michael</strong>:</p> <ul> <li><a href="https://krebsonsecurity.com/2026/09/microsoft-plugs-nearly-1000-security-holes/?featured_on=pythonbytes">Microsoft Plugs Nearly 1,000 Security Holes in Windows</a></li> <li><a href="https://pybay.org?featured_on=pythonbytes">I'll be speaking at PyBay 2026</a></li> <li><a href="https://www.pycon-nl.org?featured_on=pythonbytes">PyCon NL on October 15 in Utrecht</a> <ul> <li>Heading to Europe in October? PyCon NL is October 15th in Utrecht. One day, three tracks, about 350 people. It's an hour by train from Schiphol. There's also a session just for community organizers from groups like PyLadies, PyData, and Django. And the location fits. The Netherlands is where Python itself was born.</li> </ul></li> </ul> <p><strong>Joke: <a href="https://www.youtube.com/watch?v=FG8sUgjBGXs">You have homework</a> (no really ;) )</strong></p> <p>Watch <a href="https://www.youtube.com/watch?v=FG8sUgjBGXs"><strong>Interview with Big Data engineer in 2026</strong></a> by Kai Lentit</p>
23 Sep 2026 3:45am GMT
22 Sep 2026
Planet Python
PyCoder’s Weekly: Issue #753: frozendict, pytest Plugins, re.prefixmatch(), and More (2026-09-22)
#753 - SEPTEMBER 22, 2026
View in Browser »
Python 3.15 Preview: frozendict
Preview Python 3.15's frozendict: build immutable mappings you can hash, cache, and share across threads, and learn where the freeze stops.
REAL PYTHON
claude -p "migrate our CI config to RWX"
Python is powering AI. Your build platform should keep up. RWX is CI/CD rebuilt for the agentic dev cycle: content-based caching, graph-based task execution, and agent sandboxes that share config and cache with CI. Teams at Honeycomb, ClickFunnels, and Verkada have already switched. See Why Engineers Love RWX → rwx.com
RWX (READWRITEEXECUTE) sponsor
pytest Plugins That Actually Change How You Test in 2026
Seven pytest plugins beyond the built-ins that changed how Peyton tests: pytest-httpx for HTTP mocking, pytest-randomly for order-dependency detection, anyio's built-in pytest plugin for backend-agnostic async tests, syrupy for snapshot testing, pytest-watch for continuous feedback, freezegun for deterministic time, pytest-cov for coverage enforcement.
PEYTON GREEN • Shared by Anonymous
Soft-Deprecating re.match()
To provide consistency with other languages, Python 3.15 is soft-deprecating re.match() and introducing re.prefixmatch() instead. This article explains why.
HUGO VAN KEMENADE
Articles & Tutorials
Serve the Change Password Well-Known URL
When a password manager detects that a user's password has been leaked, it can prompt them to change it, but the password change URL varies by site. The answer to such discovery problems is .well-known/ URL namespace. This article talks about how to build it in Django.
ADAM JOHNSON
Getting Started With Rust as Python Devs
Talk Python discusses Rust for Pythonistas with guest Christopher Trudeau. Learn why Rust is a go-to language for Python tools and extensions and how you can get started writing Python modules with PyO3.
TALK PYTHON podcast
Make Public API Tests Repeatable in Python
Network-dependent API tests can be slow and flaky. This article shows how VCR.py records real HTTP interactions once and replays them for fast, repeatable, offline tests. Use cases include GitHub API client and pytest, CI-safe testing, and live API tests.
CODECUT.AI • Shared by Khuyen Tran
Primer on Jinja Templating
With Jinja, you can build rich templates that power the front end of your web applications. But you can use Jinja without a web framework running in the background. Anytime you want to create text files with programmatic content, Jinja can help you out.
REAL PYTHON
Announcing the PSF Strategic Plan 2026
In May, the Python Software Foundation (PSF) shared the high-level goals of their Strategic Plan. It was sent out for community feedback and now has been adopted. It is a five-year plan covering 2026 to 2031.
PYTHON SOFTWARE FOUNDATION
UnboundLocalError: It's Scope Decided in Advance
The variable has a value elsewhere in your program, just not in the scope Python already decided this line belongs to. Here's how Python actually assigns scope, and why the fix usually isn't global.
SYSTEM CRAFT PRESS • Shared by Bob Morrison
Functionally Zen
Practical tenets for Python simplicity: why pure functions, immutable data, and composition reduce complexity, cut mocking overhead, and keep side effects contained.
KYLE ADAMS
How to Review AI-Generated Python Code Efficiently
Learn an efficient workflow to review AI-generated code in Python: run ruff, mypy, bandit, and pytest, then catch the bugs agents get wrong.
REAL PYTHON
Agentic Engineering in Python: From Vibes to Evidence
Move from vibe coding to agentic engineering in Python, using tests, types, and code review to prove an AI agent's changes are safe to keep.
REAL PYTHON
Automating EDA With fg-data-profiling
Automate exploratory data analysis by transforming DataFrames into interactive reports with one command from fg-data-profiling.
REAL PYTHON course
5 Design Patterns Used in My New Habit Tracker App
Bob has built a Django + HTMX based habit tracker. This article covers five small Python and design patterns used in his code.
BOB BELDERBOS
Projects & Code
fastapi-security-headers: OWASP Security Headers for FastAPI
GITHUB.COM/ALEJANDROTG-CODE • Shared by Alejandro Tacoronte González
Events
Weekly Real Python Office Hours Q&A (Virtual)
September 23, 2026
REALPYTHON.COM
Django on the Med 2026
September 23 to September 26, 2026
DJANGOMED.EU
PythonCamp Rügen 2026
September 26 to September 28, 2026
BARCAMPS.EU
Python Sheffield
September 29, 2026
GOOGLE.COM
Python Southwest Florida (PySWFL)
September 30, 2026
MEETUP.COM
PyBay 2026
October 3 to October 4, 2026
PYBAY.ORG
Happy Pythoning!
This was PyCoder's Weekly Issue #753.
View in Browser »
[ Subscribe to 🐍 PyCoder's Weekly 💌 - Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]
22 Sep 2026 7:30pm GMT
Django community aggregator: Community blog posts
DjangoCon Chicago 2026 Highlights
DjangoCon US returned to Chicago in 2026, bringing together members of the Django community for a week of learning, connection, and collaboration. I caught up with a few members of the Caktus team to hear about their favorite talks and takeaways from this year's conference.
22 Sep 2026 7:00pm GMT
Generalization as discipline
General code comes out better than code cut to fit one job, and its authors are the first to benefit. When we cannot afford all of it, the way down runs against instinct: work out the ideal shape first, then cut what today does not need, and keep a plan for putting it back.

22 Sep 2026 10:00am GMT
21 Sep 2026
Django community aggregator: Community blog posts
First Aid Kits: Bleeding Control & Tourniquets
Should you put a tourniquet in your first aid kit? Maybe, but you should know some things before you do:
-
Training is more important than any piece of gear. Most bleeds can be stopped with direct pressure and/or proper wound packing - and both can be done with your hands and any old piece of fabric you have lying around. If you don't know what "direct pressure" or "wound packing" means, or if you want to practice, take a course! A Stop The Bleed course is an excellent investment, and will go into all forms of bleeding control (including how to properly apply a tourniquet.) Most basic first aid courses will also cover bleeding control to some extent, though wilderness-oriented courses (for example, Wilderness First Aid) will typically go into more depth.
(These are US-oriented suggestions; readers from other areas, I'd love for you to get in touch and let me know the equivalents in your country.)
-
Once you do take that training, you'll learn that the most important supplies for bleeding control are gloves, a stretchy bandage and plenty of gauze. Get those first, then think about other supplies.
-
You probably don't need a tourniquet in your first aid kit. Major bleeds that require tourniquets and aren't immediately fatal - are rare outside of some specific circumstances (see below). Most people will never encounter one of these circumstances. While there's nothing wrong with having one (if you're trained to use it), they're expensive and don't last forever. If you've got disposable income, and the training, sure, go ahead; but for most people, your money is better spent on something else. (Narcan, for example: you've got a much higher chance of saving a life with Narcan than with a tourniquet.)
-
You probably do need a tourniquet if you:
- work with highly dangerous power tools (e.g. table saws, chainsaws),
- use firearms (e.g. target shooting, hunting) or are likely to be shot (e.g. military), or
- ride a motorcycle.
This isn't an exhaustive list; you may be able to think of other situations. The common factor is exposing yourself to a risk of sudden massive hemorrhage. If you're in one of those situations, you should have a tourniquet and know how to use it.
-
If you do decide to have a tourniquet, you need to know how to use it. They're not intuitive to use, especially if you're stressed, and putting one on yourself can be tricky. Most people, without training, don't make the tourniquet nearly tight enough - they need to be shockingly tight.
Take a course, and buy a trainer tourniquet to practice. You don't want to practice with your real tourniquet, because they're single use. Tightening them correctly can weaken the strap to the point that they may not work a second time; hence the need for the trainer. (This is another reason why I say that tourniquets are expensive.) Practice applying a tourniquet to yourself and to someone else, and crank it down correctly. It should cut off circulation (that's the point), and will hurt. Practicing like this is safe as long as you don't leave the tourniquet tightened for longer than a few minutes.
-
Buy a windlass-style tourniquet- that's the kind with a velcro band and tightening stick. Other styles (like the stretchy strap you might see when you get blood drawn for lab work) don't work as well, and improvised tourniquets (e.g. a belt or climbing rope or whatever) don't work at all. Get it from a reputable source: places like Amazon often sell fakes that'll break under proper tension. They should be expensive (around $40 each).
The gold standard are C-A-T tourniquets; I get mine from North American Rescue.
-
An alternative to a tourniquet that's nearly as effective and easier to use is an Israeli Bandage. They're much cheaper (typically less than $10 each), and are easy to apply (but still practice). They're great, you should have one in your FAK even if you also have a tourniquet.
-
Some people put clotting agents or bandages (e.g. QuikClot) in their bleeding control kits. Opinion on these is split: they do work, but they leave gunk in the wound that your ER doc is going to need to cut out. This'll prolong recovery and cause worse cosmetic outcomes. They also can't stop the big bleeds that you'd need wound packing or a tourniquet to stop. Many ER doctors and nurses I've talked to don't like clotting agents, and recommend against them; but on the other hand, several remote/austere medicine practitioners I've spoken to make compelling cases for them. So: think about the tradeoffs.
-
Tourniquets expire - the nylon weakens over time, to the point that tightening an old tourniquet might break it. So that tourniquet your friend kept when he left the army should go in the trash, and you need to check your kit yearly-ish and replace any past their expiration date.
So what do I do?
I have tourniquets in two places:
- In my shop. I have a table saw in there, and several other things that spin sharp metal at high speed.
- In a small first aid pouch that I wear on my belt when I'm using my chainsaw.
Both of those bleeding control kits also have an Israeli bandage, vetwrap, and gauze. I probably ought to have gloves but I don't; the person doing the bleeding is going to be me or a loved one, and I can't be bothered to keep replacing them as they get all gross and sticky from the heat.
None of my other first aid kits have tourniquets. My backcountry kits don't need them; I'm not going to encounter a situation in the wilderness that requires a tourniquet. (I would add one if I went on a hunting trip.)
There's an argument to be made that I should carry one in the kit in my car - because I have emergency medical training, I'd want to stop and help if I witnessed a traffic accident, and those are situations where perhaps a tourniquet could make a difference. However, I'm concerned that the heat of the car will degrade the tourniquet faster than I'd expect, and so if I did apply it, it could fail. And I'm confident enough in my ability to control bleeding with pressure until EMS arrives.
Was this helpful? I've been thinking about writing a longer series about first aid kits (how I think about building them, and what goes in mine), of which this could become a part. If that's something you'd like to read, get in touch.
21 Sep 2026 5:00am GMT
06 Sep 2026
Planet Twisted
Glyph Lefkowitz: ... but what about video games?
I get asked this rhetorical question a lot, in various forms:
Sure, datacenters might use a lot of energy, but you don't have to use a hosted frontier model to do software development. What if I just run a local open-weights model to do some coding, with an open-source coding agent? Video games also use my GPU. Is local model development any worse than playing a video game?
So I want to write down my comprehensive answer to this: Yes, using an LLM to write some code is worse than playing a video game, for a few reasons.
Video Games Are Interactive, LLMs Are Batch Jobs
Video games use compute to respond to human input. You are using your GPU while you are looking at a screen, displaying an image. When you are done playing, you shut off the game, and your computer goes back to idle. It's much less energy. By contrast, agentic loops with evals (the only kind of "AI" that is meaningfully any good at coding) are running hot, for days. To use the most recent example of such a thing, a very rough first sketch of an implementation of a Windows graphics API backend to help port a paint program to other platforms, it took 3 weeks of Claude time, "day and night". Do you play a lot of video games for 500 hours to make it past the tutorial level, while also using other computers for other things, as well as the rest of your carbon footprint?
Video Games Need Development, LLMs Need Training
Video games use compute to respond to human input during development, too. Your game has to be made, but your LLM has to be trained. LLMs use a historically extreme amount of power, probably using more than the entire Internet, but it's kind of hard to say. Still, it seems a reasonable estimate to within several orders of magnitude that even over a multi-year project with hundreds of developers, the power used to develop an individual video game is nowhere close to training even a small LLM.
This is true even for local models. OpenAI has openly claimed that DeepSeek "stole its intellectual property", and I have heard grumblings that none of the open-weights generalist models could realistically exist without the massive lift that the frontier labs are doing with their training, in various other ways too. Secrecy throughout the industry makes this kind of impossible to understand rigorously, but it seems fair to say that you are partially culpable for all that famously energy-intensive frontier lab training if you're using a local model.
And They Keep Needing Training
You also can't dismiss this as a sunk cost, because in order to stay current with industry developments, models need to be updated with new information from the rest of the world, which means that you need to keep training them. Beyond the energy for your own use, if you want a real-life agentic workflow that actually does useful stuff, practically speaking you would still need to update your local models over and over again, at least once every few months, which means you would be incentivizing continued energy consumption by whoever was doing that training for you, including the energy cost of scraping.
Let's Be Real Here, You Aren't Actually Using A Local Model
This question is a hypothetical thought experiment. Despite synthetic benchmarks that keep showing there isn't much difference between open weight and frontier models, nobody's actually using local models for much of anything beyond sharing those talking points. Depending on which benchmark you're looking at, maybe it's good enough or maybe it's worse.
As an inveterate AI hater, all these systems seem pretty bad to me, but it seems that people who find them useful tend to subjectively believe the frontier models are worth the premium, and that's what they're actually using. Once you have accepted that it is OK to use LLMs for coding at all, it seems like a very quick slippery slope on down to "we'll go ahead and use the frontier models for now anyway, but we could be ethically better in the future by switching to an open weights one, that option is always available".
There's A Reason We Have Data Centers
Devolving power usage to local LLMs might be good to make users responsible for their costs and decrease the impacts to communities that are physically next to huge concentrations of power utilization, not to mention generation. However, there's a reason that it makes sense for the providers to build these giant facilities: economies of scale reduce total power consumption, they don't increase it. If you do all the same stuff with a local model that they have to do in hosted environments, it will probably take more power, even though you will be incentivized to do different stuff. This incentive to "do different stuff" is why although local models can hypothetically hold their own against the frontier labs for some tasks, when people or businesses take their inference costs in-house they often find that it's too painful and move back to hosted LLMs.
There Are Problems Other Than Power
These are subjects for a different post, but you have to consider a lot of other externalities: AI psychosis, de-skilling, comprehension debt, cultivating a dependency, introducing security defects, limiting your design space based on what LLMs can understand, context rot, wasting time on invalid solutions, introducing unpredictability into your workflows. You still have to consider the total cost benefit ratio.
To Sum Up
Local LLMs might alleviate some of the harms from using the hosted frontier providers. There are fewer privacy concerns, you can measure your power utilization and be more directly responsible for it, you can build interfaces with affordances that are less oriented towards addiction and dependency than the major frontier labs' harnesses.
But they're not automatically "the same as playing a video game" just because they can use the same GPU.
Acknowledgments
Thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor!
06 Sep 2026 10:57pm GMT
06 Aug 2026
Planet Twisted
Hynek Schlawack: Production-ready Python Docker Containers with uv
Starting with 0.3.0, Astral's uv brought many great features, including support for cross-platform lock files uv.lock. Together with subsequent fixes, it has become Python's finest workflow tool for my (non-scientific) use cases. Here's how I build production-ready containers, as fast as possible.
06 Aug 2026 12:00am GMT
23 Jun 2026
Planet Twisted
Glyph Lefkowitz: Adversarial Communication
As I have discussed in previous posts, "AIs" can make mistakes. In fact, they do make mistakes, and their mistake-making patterns are such that where and how they will make mistakes is both uncertain and constantly changing.
Thus, in any scenario where you want to attempt to make "productive" use of "AI", you must have a system in place for checking every result. Not checking some results; checking every result. If each result might have a consequence for you (and if it didn't have a consequence, why bother automating it?) and you cannot predict in advance which kinds of results will need verification, then verification is always required.
The verification often ends up being just as expensive as doing the work in the first place, which means that if you want your usage of "AI" to be personally profitable, you have to find someone else to externalize the cost of verification onto. This person becomes your adversary, and, if you are successful, your "AI's" victim.
The Ladder-Climber And Their Reverse-Centaur Rungs
One way that this constellation of facts can straightforwardly assemble themselves into a dystopian nightmare is the phenomenon, described by Cory Doctorow, of the reverse centaur. This is when your employer non-consensually turns you into the verification system. The "AI" does the fun part of initially performing the work, and then you do the boring part where you check if the robot is right and clean up its messes, even if everyone already knows that it would, in aggregate, be cheaper for you to do the work in the first place.
Reverse centaurs can be made from any automation, not only "AI" automation. I think that there is a reason that this term happens to have emerged in the "age of AI", though, and not with earlier automation technologies (even those which were considerably more viscerally horrific). That reason is: the wrongness of "AI" output is not merely a technical feature that must be compensated for, it is a generalized externality.
As I mentioned above, if you are responsible for the entirety of the work, both extruding the "AI" output and checking it, it's usually cheaper to have humans do the entirety of the work to begin with. When humans do the writing directly, we can check as we go, and thus verification doesn't need to be as comprehensive.
When "AI" coding advocates say "code review is the bottleneck", what they are observing is that the LLM is still rolling the dice for each PR, and a human is still necessary to verify that each of those rolls is a winner. But calling this process "code review" is a bit of a misnomer; it's not really "code review" in the traditional sense, it's human understanding.
Before the advent of "AI", the human understanding was implicit in the process of writing the code in the first place1, and the code review was a way of diffusing and extending that understanding. Now that the code can be authored with no initial understanding taking place, that cost has not gone away, it has moved.
Human understanding was always the bottleneck.
However, this is taking a collaborative view of a software project, where satisfying the needs and solving the problems of your customers are the goals. We can see that "AI" is a bad tool to satisfy those goals, because all it's doing is converting the first half of the work, that of understanding the code as you write it, to understanding the agent's output as you read it.
What if, instead, we were to take the view that every software company is a Hobbesian nightmare, red in tooth and claw? In this view, the only goal of a software project is for the individual developers to make their promo cycles and get their bonuses. Given that there is only a certain amount of money to go around, this is a zero-sum game where each programmer wants to look more productive than their colleagues.
Pretty much every organization finds it easy to reward "productivity" as expressed by lines of code emitted, but the benefits of doing thorough and thoughtful design, analysis, and code review very difficult to reward. In this world, an LLM is an invaluable tool for the sociopathic ladder-climber, particularly if your legacy organization is still structuring their workflows as if the person prompting the bot is "writing" the code, and then they get to foist off the act of "reviewing" the code onto someone else.
Here, the prompter effectively externalizes the cost of the LLM's failures but internalizes any benefits. The prompter will vibe-code a big feature, so large that the assigned reviewer can't possibly comprehend it all effectively. When this happens, the reviewer will, eventually, be pressured to approve it, even if they can try to spot a few problems along the way. The reviewer has their own work to get back to, after all, the obligation to review the prompter's (read: the bot's) code is a drain on their time that they are not going to get rewarded for.
If this feature is a big success, the prompter gets a promotion. If it causes a big issue, well, the reviewer must not have been careful enough.
This is why LLMs are "good for coding", and also why their biggest promoters keep having outages.
The Generative Gish Galloper
Coding is the biggest "success story" of this type of adversarial communication, but it is by far not the only instance of such a thing. LLMs create a new form of leverage that can turn Brandolini's law from a linear advantage into an exponential one. If you are engaged in a political debate where you want to overwhelm the other side in nonsense, an LLM can generate bullshit faster than it is physically possible for a human being to type, let alone respond thoughtfully. There is an asymmetry to the utility of this weapon as well: only one side of the political spectrum wants to flood the zone and destroy trust in institutions and the concept of truth. There's a good reason that the fascists love it.
Straightforward Spam and Fraud
This is kind of obvious, but LLMs can generate lightly-customized, plausible-looking text much more quickly than any human being. This facilitates their use in fraud, spam, and scams. In a spamming or fraudulent interaction, once again, the costs are externalized onto the victim: the recipient of a spam message has to do all the work of "checking" the LLM's output. Spammers already expect very low hit rates from boilerplate, and if the LLM can increase those percentages from 1% to 5% the technology will pay for itself; they don't need anything like reliable accuracy.
Customer "Support"
If you have any kind of commercial relationship with a company, I probably don't even need to mention this: customer "support" bots are a misery. Everybody knows it at this point. But customer support is usually conceptualized by businesses as an adversarial interaction, because it is a cost center. They maintain internal metrics on time-to-resolution and try to optimize them. Implicitly, this creates a dynamic where the goal of the customer service agent's job is not to solve your problem, but to emit noise that will cause you to think your problem is resolved, or to give up, as fast as possible. Unsurprisingly, LLMs can emit this noise faster than humans can, getting those customers off the phone. But those customers will remember those interactions, and the story outside the TTR metrics is horrible.
Similarly to the situation in software development, LLMs can look very good on paper for customer support, but mostly what they are doing is illuminating the problems with the industry's existing metrics, by turning "winning the metrics battle against the customer" into a more obvious and immediate defeat for the company's long term reputation.
"Education"
In 2026 it is sadly a fact of life that students cheat all the time using "AI", and that this cheating is very successful, in that the teachers find it very hard to detect.
LLMs are great for cheating on schoolwork because the student is externalizing the work of the checking onto the teachers, who are often starting at a disadvantage to begin with, at least in the US.
My view is that this is happening because of a divergence in the way that students vs. teachers (or, more accurately, "the broader educational system") view grading.
When a student is asked to write an essay, the teachers see the effort as both intrinsically worthwhile for the student, as well as useful as a pedagogical tool to evaluate and react to the student's progress. The student, by contrast, sees a stumbling block designed to knock them off the path to success and into a permanent underclass. It is no wonder that the student sees "AI" as useful to their own goals and has no compunction about deploying it.
There is a bitter irony that the ability to understand the inherent value of actually writing the essay on their own is the sort of thing that students can really only learn by writing a bunch of essays. There's no way that I can think of which makes the benefit legible as long as a shortcut is available.
The net effect here is a downward spiral, where the already-wobbling educational system is sustaining an attack that it doesn't have the resources to recover from. The individual students' attacks against their teachers and their schools' grading systems might appear to momentarily succeed, but they will win the battle and lose the war.
Spamming "For Good"?
Usually when we talk about someone unilaterally choosing to enter into an adversarial relationship, that's an "attack" and for good reasons we have a negative impression of the attacker. However, I would be remiss if I did not point out that there are some cases where the relationship was already adversarial; just because you're the attacker doesn't mean that you are evil.
For example we might imagine use-cases like automatically filing appeals for prior authorizations against health insurance. It's relatively well-known at this point that the main way for-profit insurers maintain their margins is by denying claims right up to the line of the policies themselves being fraud, so using a spamming tool to fight them might be entirely justifiable2 in that case.
Similarly, using an LLM could be justified in a fight against a company refusing to honor a warranty. One could imagine using an LLM to immediately generate replies and escalations.
However, even in imagined cases like these, the underlying problem is that the insurers and the vendors already have a tremendous amount of structural power, so it is more likely that they will have the advantage in deploying a communications weapon like an LLM, as well as enacting policies to simply ignore any LLM-based communication that you might submit. Worse, if these strategies were to become widespread, they might provide an excuse to reject any communications by feeding them into an unreliable "LLM detector" and issuing an automated "computer says no" even to hand-written correspondence.
It is also worth stressing that these cases are imagined, as compared to the very real coworker-abuse, spam, scam, fraud, and disinformation campaigns being waged in real life today.
Therefore, while legitimate uses might exist, it's hard to imagine that there's anywhere they would be genuinely valuable and sustainable. In the best case "AI" will provide a temporary advantage for underdogs that will provoke an arms race which the resource-advantaged adversaries will win in the long run, in the worst case the arms race itself will cement permanent structural change that will make things worse.
"Search" By Stealing
Most of the adversarial utility of "AI" is on the "write" side, since write-amplification is more obviously aggressive than reading. But the "read" side of LLMs - summarization and question-answering - can be a form of attack as well.
To begin with, the act of reading itself is currently enormously destructive, but that's arguably not a fundamental aspect of this technology. They could set reasonable rate-limits and respect things like robots.txt, as search engines have for decades now. They could also refrain from committing criminal levels of copyright infringement. But, today, using "AI" tools does suborn this sort of out-of-control crawling.
More insidiously, consider the scenario described in this YouTube video. The LTT Bros decided to try Linux again, and in the course of so doing, they had problems. When trying to solve these problems, they were faced with a choice: they could consult Reddit, or they could ask an LLM. Asking an LLM would "gaslight the heck out of" them, but they still found it preferable, because they would at least get an answer without getting yelled at.
Initially this sounds great. But it also means that you want to extract knowledge from a community, while mechanically eliding any values or norms that the community may want to impart as part of offering that knowledge. As someone who spent many years in a community tech support role, this is worrying. Many requests for support are people asking how to do things that will momentarily solve a superficial problem but create a long-term reliability problem or even an immediate security risk, that the question-asker doesn't want to hear about. Consider the question "I'm tired of entering my password so much, how do I make it so my laptop unlocks automatically". An obsequious chatbot will helpfully tell you how to do this without pushback.
But, this is also a sort of ethically murky area. The Linux community is somewhat famously, for many years now, a toxic cesspool of general hostility, misogyny, etc. It is certainly a good thing that people can get access to this knowledge without subjecting themselves to abuse. But it also means that the people with the power and the privilege to change the community for the better can just quietly withdraw, rather than fixing the problems. It also means that the positive elements of culture cannot be transmitted, and people will have no opportunity to learn about unknown unknowns.
In this case, the "adversarial" communication is with society. The thing that using an LLM for search lets you do is withdraw from society and avoid forming any personal connections. There are some personal connections which are painful and annoying, and so that can feel like a momentary balm. But the need to make connections in general is, like, the concept of society itself.
Who Am I Hurting?
LLMs are good at adversarial communication. They are so good at it, relative to their other benefits, that they will tend to make communications adversarial if you are not remaining vigilant about the possibility that it might do so. My request to you, dear reader, if you are going to use such tools, is to always ask yourself, "who might I be hurting, if I use an LLM for this?"
If you're using an "AI", who is its adversary? If you haven't given it one yet, who might the "AI" turn into an adversary? Who might you overwhelm with an asymmetric amount of output, or, if you're receiving information and not sending it, who are you taking that information from without consulting?
Figure out the answers to these questions and conduct yourself accordingly; the answer might be "yourself".
Acknowledgments
Thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor!
-
One of the reasons that software developers tend to prefer greenfield development is that when you are given a blank page, you can project your own specific understanding onto it. You can structure the codebase in a way that works for your brain, down to the variable naming conventions and the module layouts. LLM-assisted development makes everything into instant brownfield work, which makes developers instantly miserable; even those who are excited about the technology will frequently complain about how it feels like their agency has been stolen and their joy in the work has been diminished. But I digress. ↩
-
Modulo the massive amount of other externalities involved in using LLMs, of course, but I don't have the time or energy to get into those here. ↩
23 Jun 2026 8:06pm GMT