24 Sep 2026
Planet Python
Django Weblog: DSF member of the month - Ken Whitesell
For September 2026, we welcome Ken Whitesell as our DSF member of the month! ⭐

Ken has long been part of the Django community, serving for many years as an helper in the Django forum and recipient of the Malcolm Tredinnick Memorial award. He has been a volunteer for DjangoCon US for over ten years. He is a DSF member since September 2017!
You can learn more about Ken by visiting Ken's blog and his GitHub Profile.
Let's spend some time getting to know Ken better!
Can you tell us a little about yourself? (hobbies, education, etc)
Hi everyone! I've been retired for a little more than a year now. After 45 years working in software development, I'm now free to spend my time working on what interests me. (You could say that what used to be my hobby is now my profession.) I'm currently living in the mountains about 60 miles south-east of Pittsburgh.
I mostly consider myself a boardgamer. Boardgaming has been a serious hobby for me for more than 50 years now, and has been one of the driving forces in my personal development as a programmer. Not in terms of programming or developing games, but doing analysis. I enjoy looking at the probabilities and patterns that may exist in particular games. To a lesser extent, I'm starting to develop an interest in using AI to create computer opponents.
My education was primarily informal. I had a BASIC class in high school, along with a touch of Fortran and COBOL. College didn't go nearly as well - I withdrew about mid-way through my first semester. (I guess I would say that I really wasn't ready for it on a number of different levels.)
About nine months later, I enlisted in the US Air Force. That's where my education really took off. I had a variety of experiences that exposed me to technologies that I wouldn't have otherwise been able to see. The training opportunities were amazing, and I'd like to think I took advantage of most of them. Beyond that, the rest of my education has been primarily self-directed.
How did you start using Django?
I was first exposed to Django at the first PyCon after it was released, but my job and other interests prevented me from looking at it in detail. It wasn't until 2014 when I took a job to help build a system using it that I got serious about learning it.
What other framework do you know and if there is anything you would like to have in Django if you had magical powers?
Oh my. We could split a lot of hair trying to define "framework" and "know". To some degree of knowledge I've worked with Drupal, WordPress, Joomla, CherryPy, Nevow, Spring, meteor.js, LifeRay, ASP.NET, and Zope. (There may be others that I've worked with but don't remember right off-hand.)
Aside from my professional responsibilities, my pet project is something I've been trying to do for more than 20 years. I've looked at a large number of frameworks
Is there anything from those other frameworks I'd like to have? I've never honestly thought about it in those terms. Every other framework I've used has come up short in at least one area I consider critical - that's why I've looked at so many of them.
I won't say that Django is perfect, but it is a perfect fit for me, how I look at the web, what I want to do, and how I want to approach those solutions. It's the closest match to how I think. I encounter a lot less friction when trying to get stuff done with Django than I ever had in any other framework.
What projects are you working on now?
My primary project is my boardgame-playing engine. I've got a handful of games that I enjoy playing, but are ignored by the "mass market" sites such as Boardgame Arena and Tabletop Simulator. I'm doing browser-based ports of them to support competitive play of those games. I also have a couple websites I maintain for my boardgaming community.
Which Django libraries are your favorite (core or 3rd party)?
First and most important to me is Channels. My boardgame site wouldn't exist if I didn't have a "Django-friendly" way of using websockets. Related to that, but not a Django library, would be HTMX. It provides the browser-side of the websocket connection in a way that works for me.
Beyond that, I rely heavily upon Django Crispy Forms, Django Extensions, and Treebeard.
What are the top three things in Django that you like?
Top of the list is the basic fact that it's written in Python. Given the choice, I'll pick working in Python over any other language.
Next is the ORM. I don't mind working with SQL, but I appreciate the layer that builds objects from the results.
Last of the three would be forms, both their ability to render HTML and to validate input.
You have been helping community members in the Django forum for many years and for some time in Django Discord. Thank you so much for your help. What made you start helping people in the first place and keep doing it?
Community has always been important to me. When I went to my first DjangoCon in 2014, I knew I had a lot to learn. Fortunately, there were a number of Djangonauts willing to spend their time helping me get past a number of conceptual hurdles.
At DjangoCon 2018 and 2019, Carlton Gibson gave talks titled "Your web framework needs you!" They were more than inspiring, they were reminders to me that as a member of this community, I had a responsibility to help keep it going. (In my case, that meant doing more than just working the registration desk.) I started looking for ways that I might be able to contribute.
Also in 2019, Andrew Godwin announced that the Django Forum was launched. I had been reading the mailing lists, but I wasn't particularly thrilled with that as a medium for discussion. I was looking forward to the forum becoming an active venue. I wasn't active at first - I didn't have any specific questions needing to be asked and there weren't any questions posted where I felt confident enough to answer them.
We all know what 2020 brought. In my case, it brought a lot of free time I wouldn't have otherwise had. In Feburary, I saw a question I felt comfortable answering. Then in March, I saw a couple more. I did my best to answer them. Since I wasn't getting corrected or told that my comments were unwelcome, I kept going. (Even after working with Django for 5 years at that point, I still wasn't all that comfortable with my understanding of a number of concepts.) Then during that summer when all my other plans had gone out the window, it became a major part of my day. I also began to realize that I was actually contributing to the Django Community and it felt good! I knew that I would never be able to really contribute code. But I also thought if I spent my time answering questions, then the people working on the code wouldn't feel the need to do that, and so indirectly I was helping move things forward by reducing at least one roadblock from their path.
So that became my motivator. I doubt I'll ever submit a PR. But if I can do anything that helps the Fellows or the other significant developers move Django forward, I'm going to be quite happy with that.
My activity on Discord is rather limited. For quick questions or open ongoing discussions I guess it's ok. But it would be my last choice of a platform to try and help someone with a detailed technical question.
You are a Malcolm Tredinnick Memorial award recipient, could you tell us a bit more about this award and what does it mean to you?
As far as the award itself, I think the Django docs say it best:
The award will be ... awarded annually, to the person who best exemplifies the spirit of Malcolm's work - someone who welcomes, supports and nurtures newcomers; freely gives feedback and assistance to others, and helps to grow the community.
What does it mean to me? I see it primarily as validation and inspiration. It's validation that I think I am generally successful with what I'm trying to do. And it's inspiration for me to keep doing it. It's that little voice in me that says "Yes, what you're doing is worthwhile. You are helping people."
As far as knowing him, no. I was not fortunate enough for that to be the case. Malcolm passed in 2013, before I started really using Django. However, it's almost certain that I "met" him at least once while working at the PyCon registration desk from 2005 - 2010. But about the only thing I would have said to him would have been "Hi! Welcome to PyCon! Your name please?"
You are recently part of the security team, how have you seen Django security evolve through time?
To be honest, I'm not sure I'd describe it as "evolving" as much as simply "growing". One of Django's strengths to me has always been its focus on creating a secure environment. As far as I can tell, the project has always worked toward the ideal of "Security by design". And, when vulnerabilities are exposed, they get fixed. So I don't see it so much as anything is changing - just that the amount of work is increasing.
This is a process that has become more difficult through the years as Django continues to grow. The Django project has always been fortunate in that there have been some really amazingly astute people working to keep Django secure in the face of ever-increasing threats created by bad actors and AI. I'm just hoping to be able to help maintain that effort.
You know and have used Django for long time, do you have any advice for new learners or any Django user/developer?
For new people, my pet catch-phrase is "Don't fight the framework". Learn how Django does things, and adapt your mindset to match it. Do things the Django way. Don't try to make Django look or act like whatever framework you've used before. Once you really understand what's going on, if you want to make changes, fine. But ensure you understand the implications of those changes before making them.
Also, never forget that Django is Python. Nothing will help you understand Django more than having a very strong foundational understanding of Python.
What do you think of the evolution of Django through the years?
I think the stability and consistency through the years has been absolutely amazing. I could probably pull out some of the code I wrote in 2014 for Django 1.4 and have it running today with almost no effort. Django has done a wonderful job of moving things forward without unnecessarily throwing things away from the past. This stability is one of the reasons why I appreciate Django as much as I do.
After so much time in the Django community, what makes you stay?
It's the community itself, primarily the DjangoCon community. (Yes, I know that the Django community is significantly larger than just those attending DjangoCon, but my interactions with the community primarily revolve around DjangoCon, the forum, and (now) Discord.)
People come and people go. I miss some people that came to DjangoCons 10+ years ago, but I'm meeting new people every year - and that feeling doesn't change. There's something special about us, where everyone is open to share thoughts and ideas, and being friendly and helpful when doing so.
I start looking forward to the next DjangoCon the day after the previous one ends. How do you envision the future of Django from your perspective?
Technically, mostly stable with some gradual improvements. I don't see the possibility of any change being introduced that would make me want to make major changes to what I have running today. If something were to fundamentally change within Django that would break my existing system, I'd probably stay on 6.1 until I couldn't run it any more.
Is there anything else you'd like to say?
Thank you for inviting me to share my story!
Thank you for doing the interview, Ken !
24 Sep 2026 2:13pm GMT
LernerPython blog, from Reuven Lerner: New on the LernerPython practice system: A visual debugger

I'm a big fan of exercises, which is why the LernerPython platform includes hundreds of them - all using my in-browser practice system, which handles Python, Pandas, and Git, along with an AI-based Socratic tutor based on my writing.
As of today, the practice system is even better: It includes a visual debugger, allowing you to set breakpoints and trace through your Python, viewing global and local variables. It works on all of my exercises, including those involving multiple files and modules.
Moreover, the AI tutor sees the debugger's current output, allowing you to ask it questions about the current state of your program and what you're seeing.
I've got many more plans for improving my practice system in the coming weeks and months, and for making LernerPython an ever-more-attractive place to sharpen your skills.
Try a few sample exercises, with no charge or obligation: https://practice.lernerpython.com/classroom/540d7ab1a1/
And of course, you can try LernerPython free for two weeks at https://LernerPython.com/join.
The post New on the LernerPython practice system: A visual debugger appeared first on LernerPython.
24 Sep 2026 8:42am GMT
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
22 Sep 2026
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