29 Jul 2026
Planet Python
PyCharm: PyTorch Tutorial for Deep Learning
This is a guest post from Naa Ashiorkor, a data scientist and tech community builder.

Building intelligent systems that can see, hear, understand language, and make decisions was previously the domain of specialized researchers with massive computing resources only - today, deep learning has made this accessible to developers and data scientists across the world, bringing the ability to build, train, and deploy AI models within reach.
This accessibility can be credited to deep learning frameworks, and one such framework is PyTorch, which has rapidly become the prevailing choice across both research and industry. PyTorch is an open-source deep learning framework built in Python and designed to make building neural networks intuitive.
Curious about how neural networks actually learn? In this tutorial, you'll build your first PyTorch model using the MNIST dataset in PyCharm and see it recognize handwritten digits in real time. Along the way, you'll get familiar with tensors and understand the core workflow behind building deep learning models.
What is PyTorch?
PyTorch traces its roots to Torch, a scientific computing framework that used Lua; in 2016, researchers at Facebook's AI Research Lab (FAIR), now Meta AI, reinvented it for Python, creating PyTorch, which is today a Linux Foundation community project.
By 2024, PyTorch had established itself as the most popular deep learning framework, with a 63% adoption rate in the model training space, used in over 70% of AI research implementations. In 2025, the PyTorch Foundation's ecosystem grew to include large-scale projects such as vLLM, DeepSpeed, and Ray, all of which are governed independently.
The annual PyTorch Conference attracted more than 3,400 attendees and gained 16 new industry members, including Snowflake, Dell Technologies, and Qualcomm. Also, it is trusted in production by organizations such as Meta, Microsoft, OpenAI, and Tesla. For developers and data scientists looking to enter deep learning, PyTorch remains the most practical and widely supported starting point available today.
PyTorch was built on two foundations: GPU-accelerated tensor computation as a more powerful alternative to NumPy and an automatic differentiation engine for training neural networks.
From these foundations, PyTorch has grown into one of the most fully featured deep learning frameworks available. Its core features include:
- Dynamic computation graphs (define-by-run): As code executes, PyTorch builds computation graphs. These are maps of every mathematical operation your model performs: things like multiplying inputs by weights, adding biases, and applying activation functions. PyTorch needs to track these because training requires working backwards through all of those steps to calculate how much each weight contributed to the model's error, so it knows how to adjust them to improve. Computation graphs allow the model structure to be modified during runtime and facilitate debugging using standard Python tools, making PyTorch ideal for research and experimentation.
- Pythonic and intuitive interface: PyTorch code is Pythonic, which reduces the learning curve. It uses standard Python control flow and clean, readable syntax, and it integrates well with Pythonic libraries.
- Strong GPU acceleration: PyTorch has seamless support for GPUs using CUDA. There is easy device switching and efficient tensor computations on GPUs. It also supports multi-GPU training.
- Autograd (automatic differentiation): There is a built-in autograd engine that automatically computes gradients. It tracks operations on tensors and enables backpropagation with minimal code.
- Rich neural network library: PyTorch provides a comprehensive module for building models. There are prebuilt layers, loss functions, activation functions, and a modular design for custom architectures.
- Extensive ecosystem: PyTorch is not just a framework - it is an ecosystem. There is a wide array of tools, even beyond the AI-specific libraries. Hence, an entire AI project can be managed under the Python umbrella from data collection to deployment.
- Model deployment support: PyTorch supports deploying models from research to production, with support for both mobile and edge deployments. What's more, it also has TorchScript for optimized execution and ONNX export for interoperability.
- Broad community and industry adoption: PyTorch is backed by Meta, and it has a large and active community. Due to Python being one of the largest programming communities worldwide, PyTorch users benefit from shared knowledge, resources, and tools. There is extensive documentation and tutorials, and it is widely used in academia and industry.
For a broader perspective on how PyTorch and TensorFlow differ, and when to choose each, check out this blog post.
Why use PyTorch for deep learning projects?
PyTorch is at the core of the current deep learning ecosystem. In recent years, it has been the framework behind some of the most influential AI models, such as Meta's Llama, OpenAI's early GPT models, and Stable Diffusion. Today, it is a popular choice for AI research worldwide.
With a 63% adoption rate, PyTorch is the industry leader in model training, according to the Linux Foundation's Shaping the Future Generative AI report. In academia, it is highly used in research paper implementations. It is preferred for research and development because of its intuitive design, which allows for easy experimentation and iteration.
Hence, researchers can develop novel architectures and test ideas simultaneously. PyTorch powers 85% of deep learning papers presented at top AI conferences.
PyTorch is a framework of choice due to its advantages:
- Debugging with PyTorch is straightforward and natural since it runs as ordinary Python. Due to its dynamic graphing and real-time execution, developers can test and make changes to models using standard Python tools like print statements and debuggers - no special setups or workarounds are required. This sets PyTorch apart significantly from static-graph frameworks, where errors mostly emerge at runtime, and it can be challenging to trace them back to their source.
- PyTorch is flexible due to its dynamic computation graph and intuitive API, so it is ideal for experimentation and rapid iteration.
- PyTorch has a thriving community. According to the PyTorch 2024 year in review, there were contributions from more than 3,500 individuals and 3,000 organizations in a single year, and its tooling ecosystem grew by over 25%. The community has built up a huge library of tutorials, pre-trained models, and extensions. In particular, Hugging Face's Transformers library, built directly on top of PyTorch, is now the standard toolkit for NLP research and development.
Understanding PyTorch tensors
Understanding PyTorch requires an understanding of tensors. Every input, output, and model weight in PyTorch lives inside a tensor. Hence, tensors are not just a data format; they are the medium through which all computation flows.
Tensors are the core data structure in PyTorch. They are like n-dimensional arrays and matrices, but unlike regular arrays, tensors can be used on hardware accelerators like GPUs. Think of tensors as an extension of numbers we are already familiar with. A single number is a zero-dimensional tensor, a list of numbers is a one-dimensional tensor, and a table of numbers is a two-dimensional tensor. From there, you can add more dimensions to represent complex data like images, videos, or audio.
Neural networks accept tensors as input and generate tensors as output - even the parameters of a neural network, its weights and biases, are stored as tensors. For a visual explanation, you can watch a beginner-friendly video on tensors and deep learning:
Tensors are similar to NumPy arrays but can also run on GPUs or other hardware accelerators. Often, tensors and NumPy arrays can share the same underlying memory, meaning that data doesn't need to be copied.
The main difference is what happens when the calculation gets serious. NumPy is for scientific computing on a CPU. PyTorch tensors can be moved and processed on GPUs in one line of code, allowing for massive parallel computation and providing significant speedups for the types of matrix multiplication common in deep learning.
This enables the kind of processing that makes training large neural networks possible.
There are basic operations with PyTorch tensors that are essential. You can view the full implementation in this GitHub repository.
Creating a tensor
The first thing you need to know is how to create a tensor. PyTorch gives you several ways depending on what your data looks like - you can build a tensor from an existing list, initialize one filled with zeros or ones as a placeholder, or generate one with random values as a starting point for a model's weights.
import torch # From a list x = torch.tensor([1.0, 2.0, 3.0]) # Filled with zeros or ones zeros = torch.zeros(3, 3) ones = torch.ones(3, 3) # Random values rand = torch.rand(3, 3) print(x) print(zeros) print(ones) print(rand)
This code snippet demonstrates different ways to create tensors in PyTorch. A tensor is created from a Python list, alongside tensors filled with zeros and ones, and a tensor containing randomly generated values. The output displays the resulting tensor structures and values, illustrating common methods used to initialize tensors for deep learning workflows.
Basic arithmetic
Tensor arithmetic works element-wise, meaning PyTorch applies the operation across every value in the tensor simultaneously rather than looping through one by one. This is what makes tensors so fast - and it is also what makes GPU acceleration so powerful, since GPUs are specifically designed to run thousands of these operations in parallel.
a = torch.tensor([1.0, 2.0, 3.0]) b = torch.tensor([4.0, 5.0, 6.0]) print(a + b) print(a * b) print(a.sum()) print(a.mean())
This code snippet demonstrates common mathematical operations on PyTorch tensors. Two tensors are added and multiplied element-wise, while functions such as sum() and mean() are used to compute the total and average values of the tensor elements. The output displays the results of these operations, highlighting how PyTorch efficiently performs numerical computations on tensor data.
Reshaping
In deep learning, you will constantly need to reshape tensors - for example, flattening a 2D image into a 1D vector before passing it into a fully connected layer or reorganizing a batch of data to match what a model expects as input. PyTorch makes this straightforward with reshape(), which rearranges the data into a new shape without changing the underlying values.
x = torch.ones(6) x_reshaped = x.reshape(2, 3) print(x_reshaped.shape)
This code snippet demonstrates how to change the shape of a tensor using the reshape() function. A one-dimensional tensor of ones containing six elements is reshaped into a 2×3 tensor. The output shows the updated tensor structure, confirming that the data has been reorganized without altering its values.
Moving to GPU
By default, tensors are created on the CPU, but moving them to a GPU - where matrix operations can run orders of magnitude faster - takes just one line. This allows the same code to run on both GPU-equipped machines and machines that only have a CPU. It is good practice to check whether a GPU is available.
if torch.cuda.is_available():
x = x.to("cuda")
This code checks whether a CUDA-enabled GPU is available using torch.cuda.is_available(). If a GPU is available, the tensor x is moved from the CPU to the GPU using .to("cuda"). This enables faster computation by leveraging GPU acceleration, which is especially useful for large-scale deep learning tasks.
Converting to and from NumPy
PyTorch and NumPy use nearly the same language, so switching between them is simple. Chances are you are already using NumPy somewhere in your pipeline - for loading data, preprocessing, or visualizing results.
PyTorch is designed to work alongside it seamlessly. You can convert between tensors and NumPy arrays in one line, and on the CPU, they even share the same memory, so there is no performance cost to switching between them.
import numpy as np
# Tensor to NumPy
tensor = torch.tensor([1.0, 2.0, 3.0])
numpy_array = tensor.numpy()
print("Original PyTorch tensor:")
print(tensor)
print("\nConverted to NumPy array:")
print(numpy_array)
# NumPy to Tensor
numpy_array = np.array([1.0, 2.0, 3.0])
tensor = torch.from_numpy(numpy_array)
print("\nOriginal NumPy array:")
print(numpy_array)
print("\nConverted to PyTorch tensor:")
print(tensor)
This snippet demonstrates interoperability between PyTorch and NumPy. A PyTorch tensor is first converted into a NumPy array using .numpy(), and then a NumPy array is converted back into a PyTorch tensor using torch.from_numpy(). The output shows that the values remain unchanged during the conversion process, highlighting seamless data sharing between the two libraries. This is particularly useful when integrating PyTorch models with NumPy-based preprocessing or analysis workflows.
Setting up PyTorch
PyCharm streamlines deep learning setup by integrating directly with Python environments and package management tools. One of its key strengths is its seamless integration with Jupyter notebooks and optional Google Colab support, allowing you to switch between local and cloud-based computation effortlessly.
Before creating the project, it is important to install uv, a fast Python package and environment manager, locally. This enables PyCharm to create and manage project-specific environments using uv directly from the Python interpreter settings.
The setup process begins by creating a new project, where a project-specific Python environment is configured through the Python interpreter settings. During this step, a uv-managed environment and a Jupyter notebook are selected, too, enabling an interactive development environment from the beginning.
Version control can also be initialized using Git within this same window. For a detailed guide on creating and working with Jupyter notebooks in PyCharm, refer to the PyCharm documentation.
From the PyCharm Welcome screen, click New Project. In the project configuration window, select Jupyter as the project type and choose uv as the environment manager under the Python interpreter settings. This creates a project-specific environment managed by uv and prepares the project for interactive deep learning development.
After the project is created, the selected Python interpreter is displayed in the bottom-right corner of the PyCharm window. The interpreter name should indicate that it is a uv-managed environment, confirming that the project is configured to use uv for package and environment management.
To install PyTorch using PyCharm's graphical interface, open the package manager by navigating to View | Tool Windows | Python Packages. The Python Packages tool window provides a convenient way to search for, install, upgrade, and remove packages without using the terminal.
With the Python Packages tool window open, enter "torch" in the search bar to locate the PyTorch package. Select the package from the search results and click Install. The same process can be used to install related packages such as torchvision and torchaudio into the uv-managed project environment.
Using Conda as an alternative
If a Conda environment is preferred, PyCharm supports Conda directly through the Python interpreter settings. A Conda environment can be selected when setting up the project, and PyCharm will manage it automatically. Refer to the PyCharm documentation for Conda environments for more details on configuring them.
Once the Conda environment is active, install PyTorch using the terminal:
conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia
For PyTorch development, I recommend PyCharm because it provides excellent support for Python, intelligent coding assistance, debugging, version control, integrated database management, and seamless Docker integration. Specifically for data science, PyCharm supports Jupyter notebooks and key scientific and machine learning libraries and integrates with tools like the Hugging Face models library, Anaconda, and Databricks.
Additionally, it is particularly well-suited for PyTorch development because it understands the framework and includes features for layer-by-layer inspection of PyTorch tensors, which is essential when exploring data and building deep learning models.
Beyond tensors, PyCharm allows you to set breakpoints in training loops, inspect tensor values, and step through model forward passes using the integrated debugger - which works naturally with PyTorch's dynamic computation graphs.
Building neural networks with PyTorch
A neural network is a system of connected layers that learns patterns from data by adjusting its internal weights through training. In PyTorch, all of these layers are contained within a single module called torch.nn . Think of it as your construction toolkit, which gives you everything you need to assemble a network without writing low-level mathematical operations from scratch.
torch.nn comes with a library of predefined layers, such as nn.Linear for fully connected layers, nn.Conv2d for convolutional layers, and nn.LSTM for recurrent layers. Hence, you can focus on designing your network rather than implementing the math behind each layer. It provides all the building blocks needed to build your own neural network.
Every module in PyTorch subclasses nn.Module. As a neural network is itself a module that consists of other modules (layers), this nested structure allows for easily building and managing complex architectures.
When you build a neural network in PyTorch, you create a Python class that inherits from nn.Module and implements two core methods:
__init__() -where you define your layers.forward() -where you define how data flows through those layers.
PyTorch's autograd system automatically builds the computation graph based on the operations performed in the forward method, enabling automatic differentiation. The backward method, which handles gradient computation, typically does not need to be implemented manually. That means PyTorch handles the math behind gradient calculations, so you can focus on building.
Model building involves more than understanding the code. There are practicalities that need to be considered.
- Constant iteration. There is a very high probability that your first model will not perform well. That is normal because deep learning is an experimental process that involves adjusting layers, activation functions, and hyperparameters until the model improves.
- Simplicity first, then complexity. A two-layer feedforward network is always a good starting point. It is advisable to add complexity, such as additional layers and different architectures, when it is clear that the simple model is insufficient.
PyCharm makes model building easier thanks to its integrated debugger. You can set breakpoints inside your forward method, inspect tensor values at each layer, and add step-throughs of your model pass by pass, which drastically reduces the time it takes to identify and fix problems.
Build your first PyTorch handwritten digit classifier
In this section, you will build a simple neural network in PyTorch that can recognize handwritten digits from the MNIST dataset. You will work through the complete workflow, starting from raw image data; you will prepare and normalize the dataset, define a neural network, train it to recognize digits, and evaluate how well it performs on test data.
Along the way, you will explore key deep learning concepts such as tensors, layers, activation functions, loss functions, optimization, and training loops, while using PyCharm to inspect and understand what happens inside the training loop.
In deep learning, image classification is a foundational task, in which a model learns to assign a label to an image based on its visual content. In this example, we'll use image classification on the MNIST database of handwritten digits, a classic benchmark in computer vision that consists of 28 x 28 grayscale images of handwritten digits from 0 to 9.
It is small and well-structured, and using it as an example gives us the opportunity to focus on understanding the core building blocks of deep learning. The aim is to build a neural network using PyTorch that can accurately recognize and classify these digits.
The complete source code for this project is available in the accompanying GitHub repository.
MNIST dataset (source)
Preparing the data
Before training any model, the data needs to be loaded, cleaned, and formatted so PyTorch can work with it efficiently. PyTorch provides two classes that handle this:
Datasetdefines how individual samples are accessed and returned.DataLoadertakes aDatasetand handles how data is fed into the model during training, including batching, shuffling, and parallel loading.
As these components are configured, PyCharm helps streamline development through features such as code completion, automatic import suggestions, parameter hints, and quick documentation.
Hovering over PyTorch classes and functions shows usage information, and pressing Ctrl+Q opens detailed documentation directly within the IDE. Hence, it is easier to explore PyTorch APIs and correctly configure data loading and preprocessing steps without frequently switching to external documentation.
As transforms.Normalize() is typed, PyCharm displays the function signature and parameter information directly in the editor, helping developers configure data preprocessing steps more efficiently without referring to external documentation.
Loading and normalizing the data
Before training a neural network, the input data needs to be normalized so that the pixel values are scaled into a consistent range. This helps improve stability by keeping input values centered around zero and ensuring that gradients behave more predictably during optimization.
# Download and load the training data train_data = datasets.MNIST( root='./data', train=True, download=True, transform=transform )
The code snippet above downloads the MNIST dataset (if needed), loads the training images, and applies preprocessing so that the data is ready to be used in a neural network.
In this project, MNIST images are normalized as part of a preprocessing pipeline using PyTorch transforms:
transforms.ToTensor()converts images from pixel values (0-255) into floating-point tensors scaled to 0-1.transforms.Normalize((0.5,), (0.5,))then rescales these values to approximately -1 to 1, which helps stabilize training by keeping input values centered around zero and improving gradient behavior during optimization.
PyTorch also provides key data-loading parameters to control how training data is processed:
batch_size=64means the model processes 64 images at a time instead of the full dataset. This improves memory efficiency and makes training more stable by allowing gradient updates on mini-groups of data rather than individual samples or the entire dataset.shuffle=Truerandomizes the order of images each epoch, so the model does not memorize the sequence.download=Truemeans PyTorch fetches MNIST automatically on the first run, so you do not need to download anything manually.
Defining the model
After the data is ready the next step is to build the neural network that will learn from it. The goal of the model is to take an input image of a handwritten digit and predict which digit (0-9) it represents. Each MNIST image is 28×28 pixels. Since the model cannot directly interpret images the way humans do, we first flatten each image into a single vector of 784 values (28 x 28 = 784). This converts the 2D image into a format the model can process.
The input layer takes the 784 pixel values and passes them through fully connected layers. Each layer learns weighted combinations of features that become increasingly useful for distinguishing digits. While these representations are not explicitly interpretable, the network gradually learns patterns that help separate different classes.
To help the model learn effectively, we use an activation function called ReLU, which allows the network to capture non-linear patterns that are essential for understanding images.
class SimpleNetwork(nn.Module):
def __init__(self):
super(SimpleNetwork, self).__init__()
self.fc1 = nn.Linear(784, 128) # 28x28 = 784 input pixels
self.fc2 = nn.Linear(128, 64) # hidden layer
self.fc3 = nn.Linear(64, 10) # 10 outputs (digits 0-9)
def forward(self, x):
x = x.view(-1, 784) # flatten the image
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
model = SimpleNetwork()
print(model)
When you run the code, PyTorch prints the structure of the model:
SimpleNetwork( (fc1): Linear(in_features=784, out_features=128, bias=True) (fc2): Linear(in_features=128, out_features=64, bias=True) (fc3): Linear(in_features=64, out_features=10, bias=True) )
The output shows the structure of the neural network. Each Linear layer represents a fully connected layer in the model. The first layer transforms the 784 input pixels into 128 features, the second reduces them to 64 features, and the final layer outputs 10 values representing the digit classes (0-9). This confirms that the model has been correctly defined before training begins.
Using the Jupyter console to inspect data and validate the neural network
One of the features that makes PyCharm Pro especially useful for PyTorch development is the integrated Jupyter console. It connects directly to the running notebook kernel, allowing you to inspect tensors, explore datasets, test model outputs, and debug code interactively without adding temporary cells to the notebook. This streamlines the iterative workflow and makes it easier to validate code during model development.
To access the Jupyter console, first ensure that your Jupyter notebook is running. Then click Open Jupyter Console in the notebook toolbar at the top of the editor.
Additionally, PyCharm provides a Variables view that displays all active objects in the notebook kernel, allowing quick visual inspection of shapes, values, and types, and reducing the need for repeated print statements.
Together, these tools make it easier to inspect data and validate model behavior before training.
The Jupyter console allows the interactive execution of code linked to the notebook kernel, so you can inspect data and test the model before training. The Variables view displays active objects for quick inspection without print statements.
Training the model
Choosing a loss function and optimizer
Now that the model is defined, the next step is to train it so it can learn to recognize handwritten digits. During training, the model processes MNIST images, makes predictions, compares them to correct labels, and gradually improves its performance. To do this, we first need two key components: a loss function and an optimizer.
The loss function measures how far the model's predictions are from the correct answers. In classification problems like MNIST (which has 10 classes, one for each digit), CrossEntropyLoss is used because it is designed for multi-class classification, and it not only penalizes incorrect predictions but also takes into account how confident the model is when it makes a mistake.
The optimizer is responsible for updating the model's weights based on the loss. It determines how the model learns from its errors.
We also need to select an optimizer. Adaptive moment estimation (ADAM) and stochastic gradient descent (SGD) are two examples of these - they take the loss and adjust the model's weights to do better next time.
The difference is how they do it. SGD updates model weights using a fixed learning rate applied to the computed gradients. ADAM extends this idea by adapting the learning rate for each parameter using estimates of past gradients, which often leads to faster and more stable convergence with less manual tuning. For this project, ADAM is the practical choice, with lr=0.001 as a safe default learning rate. SGD is worth exploring later when you want more control over the training process.
Implementing a training loop
The training loop is the core of the learning process. Each full pass through the training data is called an epoch. Training typically runs for multiple epochs so that the model can gradually improve its performance over time.
Each epoch is made up of smaller units called batches. Instead of processing the entire dataset at once, the model processes one batch at a time, which makes training more efficient and memory-friendly.
During each epoch, the model processes data in batches and repeats the same steps:
- Forward pass: The model makes predictions (logits).
- Loss computation: The model compares predictions with true labels.
- Backward pass: The model computes gradients of the loss.
- Weight update: The optimizer adjusts model parameters.
There are a few important implementation details to note when it comes to this section:
model.train()switches the model into training mode and must be called at the start of each epoch.optimizer.zero_grad()must be called beforeloss.backward()every iteration because, without it, PyTorch accumulates gradients from previous batches, which corrupts the updates. This is one of the most common beginner mistakes in PyTorch.loss.item()converts the loss tensor into a Python number for logging. This detaches it from the computation graph, ensuring it is not tracked for gradients.
The code below implements the training loop and prints the loss at the end of each epoch:
# Define loss function and optimizer
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# Training loop
epochs = 5
for epoch in range(epochs):
model.train()
running_loss = 0
for images, labels in train_loader:
# Forward pass
predictions = model(images)
loss = loss_fn(predictions, labels)
# Backward pass
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss += loss.item()
avg_loss = running_loss / len(train_loader)
print(f"Epoch {epoch+1}/5 - Loss: {avg_loss:.4f}")
The output below shows the model's training progress over five epochs, with the loss steadily decreasing as learning improves.
Epoch 1/5 - Loss: 0.4014 Epoch 2/5 - Loss: 0.1937 Epoch 3/5 - Loss: 0.1364 Epoch 4/5 - Loss: 0.1116 Epoch 5/5 - Loss: 0.0957
Debugging the training process using the PyCharm debugger
While basic Python debugging is available in PyCharm, the PyCharm Pro subscription extends this capability by providing full support for debugging Jupyter notebooks and interactive machine learning workflows.
During model training, breakpoints can be set inside key stages of the training loop, such as the forward pass, allowing execution to pause while the notebook remains interactive. For the MNIST handwritten digit classification project developed in this tutorial, the breakpoint was placed on: predictions = model(images).
This marks the start of the forward pass, in which a batch of input images is passed through the neural network to generate predictions. Pausing execution immediately before this line makes it possible to inspect the input data before the model processes it and then examine the model's outputs after stepping over the line. This provides a clear view of how data flows through the network during training.
In the PyCharm debugger, the Watches pane lets you monitor custom expressions whenever execution pauses at a breakpoint. Rather than repeatedly evaluating expressions manually, watches automatically refresh their values after each debugging step, making it easier to inspect tensors and verify intermediate results throughout the training process.
For this project, the following watches were added:
images.shape, to verify the dimensions of each input batch.labels.shape, to confirm that the batch of labels corresponds to the input images.predictions.shape, to verify that the network produces an output tensor of the expected shape after the forward pass.predictions.argmax(dim=1)[:5], to display the predicted digit for the first five images in the batch.
After stepping over the forward pass, these watches automatically update to display the model's outputs. This makes it straightforward to verify that the input tensors have the expected dimensions, confirm that the network produces a prediction for each image in the batch, and inspect the predicted digit classes without modifying the source code.
The debugging workflow described in this section is demonstrated in this video:
Model evaluation
Training is done, but a low training loss does not necessarily mean your model is good. It might have simply memorized the training data. Evaluation on unseen test data tells you how well it actually generalizes. After five epochs of training, the model achieves a test accuracy of 96.87%, correctly classifying 9,687 out of 10,000 previously unseen digits.
This indicates that the model generalizes well to new data for a simple fully connected architecture without additional optimization techniques. It also demonstrates one of PyTorch's biggest strengths in practice: You can go from raw data to a working, accurate model with relatively little code.
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
predictions = model(images)
_, predicted = torch.max(predictions, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f"Test Accuracy: {accuracy:.2f}%")
Advanced PyTorch techniques for deep learning
There are advanced PyTorch techniques that can be explored when you grasp building and training basic models. They can take your work further, for example by allowing you to train faster, scale larger, or move a model into production. Some of them include:
- GPU acceleration. One of the highest-impact changes you can make is moving your model and data to a GPU. Modern NVIDIA GPUs such as the A100, H100, and V100 are recommended to accelerate PyTorch with the greatest speedup. They offer exceptional performance, especially for features such as
torch.compile. - Distributed learning. When a single GPU is not enough - either because your model is too large or your dataset too vast - PyTorch's
torch.distributedbackend lets you scale training across multiple GPUs or machines.DistributedDataParallel(DDP) enables distributed training across multiple GPUs or machines, significantly boosting compute power and reducing training time. When the capacity of a single GPU is exceeded by your model, DDP becomes essential and requires only a few additional lines of code to set up. - Model deployment. Training a model is just one key aspect; eventually, you need to deploy it to real users. TorchServe is a flexible and easy-to-use tool for serving Python models in production. It supports deploying models in either eager or graph mode using TorchScript, serving multiple models concurrently, versioning models for A/B testing, loading and unloading models dynamically, and monitoring detailed logs and customizable metrics.
These three techniques represent the natural progression of any advanced deep learning project. You start on a single machine, scale when needed, and ship when you are ready. They are worth exploring as your projects grow in ambition.
Summary and resources
In this tutorial, you went from understanding what PyTorch is to building and training a neural network that recognizes handwritten digits with over 96% accuracy. Also, we covered tensors, the torch.nn module, the training loop, and model evaluation, which are the core building blocks of every deep learning project built with PyTorch.
This is just the beginning. PyTorch's real depth lies in what comes next - convolutional networks, transfer learning, and the vast Hugging Face ecosystem of pre-trained models, which run on a PyTorch backend, all built on the same foundations you learned here. Continue to experiment! Swap the optimizer, add a layer, and try a different dataset.
A great next step is to explore the official PyTorch tutorials, which cover everything from convolutional networks to deploying models in production. For a more structured learning path, the Zero to Mastery PyTorch course is free and beginner-friendly, picking up exactly where this tutorial ends.
Build your first PyTorch model in PyCharm
PyCharm gives you one environment for the full deep learning workflow: installing PyTorch, writing model code, running notebooks, debugging the training loop, inspecting tensors, tracking experiments, and managing your project with Git or Docker as it grows.
Download PyCharm for free and use this tutorial to build your first MNIST classifier.
About the author
29 Jul 2026 4:18pm GMT
28 Jul 2026
Planet Python
PyCoder’s Weekly: Issue #745: PyPI UI, Finding Classes with the GC, pylock.toml, and More (2026-07-28)
#745 - JULY 28, 2026
View in Browser »
Planned Updates to the PyPI User Interface
Over the next few months a new user interface will be rolled out for the Python packaging website, PyPI. The rollout will be done in phases to make sure it is rock solid and to get community feedback. This post talks about the history of PyPI's UI and what is changing.
NICOLE HARRIS
Find All Instances of a Class With gc.get_objects()
If you're debugging a situation with multiple references to an object and you want to hunt down all instances, the garbage collector module can help you out.
ADAM JOHNSON
Let AI Agents Into Your B2B App. Securely
More of your users are asking to connect AI agents to your product, and you want to say yes. PropelAuth lets you give each agent scoped, revocable access, so you stay in control of what it can do. Learn More →
PROPELAUTH sponsor
Tool-Agnostic Python Lock Files With PEP 751 and pylock.toml
Learn how PEP 751 standardizes Python lock files with pylock.toml: generate one with pip or uv, install it with uv or pdm, and retire requirements.txt.
REAL PYTHON
Articles & Tutorials
A Versatile LLM Harness & Scraping the Web With Scrapy
Which is more important, the model or the "harness" around an LLM? What are ways to assemble an efficient agentic developer workflow? This week on the show, Ayan Pahwa joins us to discuss harnessing, web scraping, and self-hosting Python applications.
REAL PYTHON podcast
Pip 26.2: -only-deps Solves Years of Deployment Hacks
When working with scripts and simpler projects, sometimes you need dependencies installed without the full package. There have been work arounds for years, but now pip 26.2 has a new flag to support this.
JAMES O'CLAIRE
[Registration Closing] Claude Code for Python Developers
By Sunday evening, you'll have built, debugged, and shipped a complete Python project with an AI agent, and you'll know how to bring that agentic engineering workflow to your own codebase on Monday. Live on August 1-2, doors close this Friday. Claim Your Spot →
REAL PYTHON sponsor
PyPI Releases Now Reject New Files After 14 Days
"The Python Package Index (PyPI) now rejects new files being uploaded to releases that are older than 14 days. This restriction was put in place to prevent old and long-stable releases from being poisoned"
PYPI.ORG
Nifty Django Feature: Form Templates
Form templates in Django allow you to make reusable pieces for forms, giving a separation between the view's template and how the form gets rendered.
TIM SCHILLING
FastAPI: Python API Development With Light Speed
Learn FastAPI from the ground up. Build REST APIs, serve web pages with Jinja2 templates, and create a complete URL shortener project in Python.
REAL PYTHON
Using NumPy reshape() to Change the Shape of an Array
Learn how to use NumPy reshape() in Python to change an array's shape, add or remove dimensions, and control how the data is rearranged.
REAL PYTHON
Security: Line Goes Up
CPython is experiencing a huge increase in security reports. This post talks about why that is happening and how it is being handled.
HUGO VAN KEMENADE
What Our AI Guiding Principles Actually Mean
Wagtail's five AI principles, from policy / guidelines to practice and how they steer responsible AI adoption for the project.
THIBAUD COLAS
Exploring Python's Built-in Functions
Learn Python's built-in functions for math, data types, iterables, and I/O, and when to use each to write more Pythonic code.
REAL PYTHON course
Projects & Code
interlock: Circuit Breaker On Failure Rate and Latency
GITHUB.COM/BAGOWIX • Shared by Bogdan Galushko
tsauditor: Statistical Auditor for Temporal Data Leakage
GITHUB.COM/IMANN128 • Shared by Iman Naeem
darnlink: Fix Relative Markdown Links When Files Move
GITHUB.COM/TXEMI • Shared by txemi
Events
Weekly Real Python Office Hours Q&A (Virtual)
July 29, 2026
REALPYTHON.COM
Melbourne Python Users Group, Australia
August 3, 2026
J.MP
PyBodensee Monthly Meetup
August 3, 2026
PYBODENSEE.COM
STL Python
August 6, 2026
MEETUP.COM
Canberra Python Meetup
August 6, 2026
MEETUP.COM
Sydney Python User Group (SyPy)
August 6, 2026
SYPY.ORG
PyCon Indonesia 2026
August 8 to August 10, 2026
PYCON.ID
Happy Pythoning!
This was PyCoder's Weekly Issue #745.
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 ]
28 Jul 2026 7:30pm GMT
Python Software Foundation: Announcing a 2026 PSF Grants Program Funding Round
The Python Software Foundation (PSF) is excited to announce a 2026 PSF Grants Program funding round. This is not a full reopening of the Grants Program as it existed before. Rather, it's what the PSF is able to sustainably offer right now, given where we stand financially and operationally. The PSF Board, PSF Staff, and PSF Grants Work Group (GWG) are deeply passionate about the program and understand how important it is to the Python community. It's our honor to have the opportunity to disburse grant funding in 2026.
In keeping with our focus on sustainability, this round of grant funding has a set budget capped at $90,000 USD, a limited scope, and a different structure and timeline for applying and reviewing. We will be accepting applications from August 4 - 25 AoE, for Conferences and Workshops that are scheduled between December 1, 2026, and April 30, 2027. Our top priority is getting available funds to the regional communities who need it most: those who have had to pause their events and initiatives because of lack of funding from PSF Grants or loss of sponsors.
Context
As folks following along with the PSF may remember, the last couple of years have been financially challenging, with the PSF's assets and yearly revenue declining and costs increasing across the board. At the same time, the demand for our work has continued to multiply. Making the decision to pause the Grants Program last year was difficult, but a necessary step to protect both the future of the program and the short- and long-term sustainability of the PSF.
The PSF acknowledges the pause created challenging situations for the many community groups that had planned to apply for the grants program. We also recognize and appreciate the community's support-both in response to the announcement of the pause and through the outstanding results of the 2025 end-of-year fundraiser.
The Python community showed up with understanding and solidarity when the pause happened and helped us come up with ideas on how the PSF could serve the community in non-financial ways. Those ideas were the seeds that grew into the PSF Community Partner Program, a non-monetary partnership offered to qualifying applicants. This program assists Community Partners by attaching the PSF name to the event or initiative, which lends credibility, helps attract sponsors, and provides promotional support through reposts on PSF social media accounts.
Funding Round Eligibility, Caps, and Criteria
Eligibility Timeframe
The 2026 Grants Program Funding Round will be narrowly scoped to Conferences and Workshops of all types that are scheduled between December 1, 2026, and April 30, 2027. If all goes well, the PSF intends to run future rounds of funding, so please do not be discouraged if your event doesn't fit within this time frame. This time frame reflects the PSF's current finances, our staff capacity, and our goal of getting funds to recipients while they're useful.
Categories of grants that will be considered (includes virtual):
- Conferences. One or more days of conference programming, with at least 60% Python content.
- Workshops (Python-related, PyLadies, and DjangoGirls). One or more days of workshop programming, with at least 6 hours per day of instruction.
The PSF also wants to acknowledge that this timeframe may exclude some events and initiatives that also missed out on funding in 2025. Please know exclusion is not our intent. If we are able to offer later rounds, we plan to prioritize events and initiatives that missed out on funding in 2025 and 2026 due to the timing windows. Getting the program back up and running is a lot of work for our small team, and we have experienced significant staffing changes in the last year. These changes have made it harder to keep pace with our regular activities, let alone get the Grants Program up and running again. What felt the most important was getting at least some funds out, even if we couldn't kick the program off right at the same time of year it was paused last year.
Adjustments to Grant Category Caps
The 2026 Grants Program Funding Round will adjust the cap for Conference type grants down to $2,000 USD and maintain the Workshop type grant cap at $1,500 USD. This change reflects a focus on supporting hyper-local communities that had to halt their activities due to the PSF Grants Program pause.
The PSF has observed, through social media, Grants Program Office Hours, and informal conversations, that many large and long-standing international PyCons are still taking place without PSF Grants, while workshops and smaller regional initiatives have completely paused or slowed down significantly. Based on these observations, the PSF estimates that $1,500 will make an impact for those workshops and $2000 could help fill in some gaps in PyCon budgets. Our hope is to empower as many groups as possible with this round of funding.
Please note that the caps are the maximum amount applicants can request. If you don't need that amount, please ask for less. The guidelines the Grants Work Group observes are generally as follows:
- Conferences: Up to $15 USD per attendee per day.
- Workshops: Up to $25 USD per student per day.
Notes on Scope, Criteria, and Communication
The PSF wants to highlight that consolidated grant types will not be considered during the 2026 Grants Funding Round. While this was a great addition for when the Grants Program was running on a rolling basis, for this limited funding round, the PSF Grants Work Group needs to look at applications on a singular level. We ask that communities that previously submitted consolidated grants submit individual applications for up to 5 conferences or workshops that are scheduled to take place during the eligibility timeframe.
All previous criteria and guidelines for the PSF Grants Program will be applied to this funding round. This post won't go over every single piece of information required on the application, but we want to highlight a couple of things:
- If the applicant has received a grant in the past for a different event, or the event itself has received a grant, a report must be submitted via our grant reporting form. This is a hard requirement due to our status as a charity based in the US, as our grant awards must be auditable to the best of our ability. We do our best to make this as small a burden on grant recipients as possible.
- The applying event or initiative must have a Code of Conduct and reporting mechanism prominently displayed on your event's website. Part of the PSF's mission is to support and facilitate a diverse and international community of Python programmers, which means that it is essential for our grant recipients to have an enforceable Code of Conduct. If your event or workshop does not have a Code of Conduct or reporting mechanism, check out our event Code of Conduct best practices documentation.
- We are unable to provide grant funding for:
-
- Personal travel requests
- Hackathons
- T-shirts, swag, giveaways, and prizes
- Events and initiatives unrelated to Python or the PSF's charitable mission
Grants Funding Round Schedule
Listed in the table below is the anticipated schedule for the 2026 PSF Grants Program Funding Round. The timeline is tight (applications open next week!), but our team hopes that three weeks to get applications in is reasonable and accommodates events and initiatives that fall in the eligibility timeframe.
| Date | Phase | Description |
|---|---|---|
| August 4 - 25 AoE | Application | Applications open; PSF Staff performs initial reviews as applications are received; any missing information is collected |
| August 25 - September 11 | Review | Grants Work Group review; clarifying information collected as needed; Grants Work Group votes |
| September 14 | Decision | Decisions communicated to all applicants |
| September 14 and onwards | Disbursement | Funds disbursed |
.table { display: block; overflow-y: hidden; overflow-x: auto; scroll-behavior: smooth; } .table table { table-layout: auto; border-collapse: collapse; } .table thead { display: table-header-group; vertical-align: middle; border-color: inherit; color: white; background: darkcyan; } .table tr { display: table-row; vertical-align: inherit; border-color: inherit; } .table th { padding: 16px; text-align: inherit; border-bottom: 1px solid black; color: white !important; white-space: nowrap; } .table td:nth-child(2) { white-space: nowrap; padding: 16px; } .table td { padding: 16px; border-bottom: 1px solid #ddd; } .table tbody { display: table-row-group; vertical-align: middle; border-color: inherit; } .table table:not(.tr-caption-container) { min-width: 100%; border-radius: 3px; }
After things kick off, the PSF may need to adjust dates by a couple days here and there. This program is dependent on just a couple of staff (Hi, Marie and Laura!) and our wonderful Grants Work Group (Thank you, team!) that is composed of volunteers. If dates need to be adjusted, we will be sure to communicate that in multiple places (Emails direct to applicants, Discuss, PSF Discord, and PSF social media accounts: LinkedIn, Mastodon, Bluesky, X).
The PSF asks that applicants closely monitor their emails from the point they submit their application to the end of the review phase. We would be disappointed to see events and initiatives miss out on grant funding due to gaps in their application. The more responsive applicants can be, the better!
How to Apply
Submit your applications via the PSF Grants Program application form. Before August 4 and after August 25, the form is still available but only taking applications for the PSF's Meetup Pro Network.
Questions or feedback?
Phew-that was a lot of information! The PSF expects questions about the 2026 Grants Program Funding Round. In fact, there may be things we've overlooked, and we would appreciate you sharing anything you think we're missing. Your feedback will help us improve during the process and for future rounds. There are multiple ways for you to reach out to us with your questions, feedback, and comments:
- Discuss forum thread
- Email grants@python.org
- PSF Grants Office Hour sessions (more info below)
Due to the accelerated nature of this grants funding round, we are holding supplemental PSF Grants Program Office Hours on the PSF Discord:
- August 4 at 1 PM UTC
- August 13 at 8 PM UTC
- August 18 at 1 PM UTC (this is our regular day/time!)
Check out what times these are for you using this timezone converter. We welcome you to join us to ask your questions, discuss the process, suggest ideas for future rounds, or anything else related to the PSF Grants Program.
Final Thoughts and Thanks
This is a big change for the PSF Grants Program. It's moved from a rolling basis, to a pause, and now to a limited window to receive, review, and make decisions about applications. Will the process be perfectly smooth? Probably not. But we are committed to doing it as efficiently as possible, keeping the community and applicants informed of any changes, and when possible, integrating feedback we receive throughout the process.
The PSF also wants to thank you, the Python community, for your understanding and generous backing, in actions, words, and donations. We could not fulfill our mission without the community's support and without each individual out there championing the PSF's work. The PSF is so very grateful to be in community with each and every one of you.
About the Python Software Foundation
The Python Software Foundation is a US non-profit whose mission is to promote, protect, and advance the Python programming language, and to support and facilitate the growth of a diverse and international community of Python programmers. The PSF supports the Python community using corporate sponsorships, grants, and donations. Are you interested in sponsoring or donating to the PSF so we can continue supporting Python and its community? Check out our sponsorship program, donate directly, or contact our team at sponsors@python.org
28 Jul 2026 8:18am GMT
24 Jul 2026
Django community aggregator: Community blog posts
Issue 347: Django 6.1 release candidate 1 released
News
Django 6.1 release candidate 1 released
This is the final opportunity to try out the new version before Django 6.1 is released. Try it, run your test suite, and report anything that breaks!
The DjangoCon US 2026 schedule has been released!
The talk lineup is out, covering Django 6.0 and 6.1 features, modern deployment patterns, GeoDjango at scale, and lightning talks across all three days.
PyPI Releases now reject new files after 14 days
PyPI will reject new files uploaded to releases older than 14 days to limit the impact of compromised publishing tokens or workflows.
Planned Updates to the PyPI User Interface
PyPI's first UI refresh since 2018 will roll out in phases over the coming months, surfacing more security signals on package pages. The first phase is staged on TestPyPI now and ready for your feedback.
Wagtail CMS News
What our AI guiding principles actually mean
Wagtail unpacks its refreshed AI guiding principles and how they steer adoption in practice, starting with a firm commitment: no AI dependency in Wagtail core, with AI features staying opt-in through packages like Wagtail AI.
Django Software Foundation
DSF Board monthly meeting, July 09, 2026
Minutes from this month's DSF Board meeting: a host for DjangoCon Europe 2027 was approved, a new Google Summer of Code Working Group was chartered, Executive Director hiring continues with guidance from the PSF, and grants went to PyCon Cameroon and PyCon Africa.
Updates to Django
Today, "Updates to Django" is presented by Raffaella from Djangonaut Space! 🚀
Last week we had 17 pull requests merged into Django by 11 different contributors - including 6 first-time contributors! Congratulations to Tom Most, CharulL00, Sina Chaichi Maleki, Harvey Bellini, Stephanie and Vismay for having their first commits merged into Django - welcome on board!
News in Django 6.2:
- The
MiddlewareMixinclass moved fromdjango.utils.deprecationtodjango.middleware. The old import path is deprecated in Django 6.2. - Whether to suppress an
ImportErrorescaping from a settings module is configurable by the newBaseCommand.requires_settingsattribute (defaultTrue). In previous versions, such errors were always suppressed. - The minimum supported version of
asgirefis increased from 3.9.1 to 3.12.1.
Thanks to the continuous efforts of the contributors, a SQLite regression test has also been added to inspectdb when a table has a foreign key that references sqlite_master. (#25243)
Support for prefers-color-scheme was also implemented, adding dark mode CSS overrides for the technical 500 (traceback) and 404 debug views. (#35875)
Django Fellow Reports
Django Fellow Report - Natalia
A security-heavy week: two patches for confirmed vulnerabilities, deep-dive reviews of two more, and prep for the August release with CVE metadata and prenotifications, plus continued iteration on EmailValidator improvements.
Django Fellow Report - Sarah
Reviews across Django and djangoproject.com, including the Selenium to Playwright migration and admin widget fixes, plus a new GitHub Action to test djangoproject.com against Django main and engagement on six security issues across Django and djangoproject.com.
Django Fellow Report - Jacob
Sustained attention on a couple of security reports, alongside triage and a long review list covering dark mode error pages, Oracle Test Pilot in CI, and the asgiref 3.12 update that enables free-threading tests.
Python Software Foundation
Get Ready: PSF Board Nominations Opening Soon!
PSF Board nominations open July 28, with voting September 1-15. If you're a voting member, affirm your intent to vote by August 25.
Get Ready: 2026 Python Packaging Council Nominations Opening Soon!
Nominations for the first-ever Python Packaging Council open July 28 and close August 11.
Events
Django Girls Chicago - August 22, 2026
Django Girls returns the Saturday before DjangoCon US in Chicago: build your first website, eat free food, and meet fellow aspiring Django developers. The free workshop is limited to 45 people and applications close August 12, so apply early.
Preparing for sprints as a project leader (at DjangoCon US)
As DjangoCon US approaches in just a few weeks time, here are some good tips on how to make the most out of the sprints following the tutorial talks.
A First-Timer's Guide to Navigating America
If you are attending DjangoCon US, please do follow the news section of the website, as it has helpful articles like this one, as well as info on childcare at the conference, and more.
Sponsored Link
When is it worth paying for a mentor?
Thinking about hiring a mentor to grow as a Django developer? Here are a few honest questions to help you get clarity on whether now's the right time.
Articles
Django: introducing django-crawl
Adam Johnson introduces django-crawl, a new package that crawls your whole site with Django's test client (via links, sitemaps, or a Python API) to surface broken pages before your users do.
Nifty Django Feature: Form Templates
Django's form templates separate a form's HTML from the view that processes it: set template_name on the form for one reusable layout, or on an individual field when a single input needs custom markup.
Some more things about Django I've been enjoying
Building a "2010 style" backend-heavy web app, this writeup highlights Django's readable query builders, handy template filters like querystring and json_script, and the comfort of automatic migrations. It also covers the author's practical performance questions, including a misconfigured cached template loader and why it mattered.
Browser Push Notifications for a Django Website
A step-by-step tutorial on adding browser Web Push notifications to a Django site using VAPID keys, a service worker, and a Huey background task, so you get OS-level notifications even when the admin tab is closed.
Is it time to go back to Django?
Some arguments for Django's opinionated, batteries-included approach in the AI-coding era, since it limits the decisions an AI agent has to make and reduces the chance of it going astray.
Deploying Web Apps in 2026: My EuroPython Conference Talk
The written version of Will Vincent's EuroPython talk, which maps today's hosting landscape and builds a ten-step mental model of everything your dev server quietly handles for you, from WSGI servers and static files to running migrations at release time.
My EuroPython 2026 - Paolo Melchiorre
A day-by-day recap of Paolo Melchiorre's EuroPython 2026 in Kraków, compiled from Mastodon posts and photos, from the Python Steering Council update to rethinking asyncio for free-threaded Python and time with the Django community at the booth.
EuroPython 2026 Recap - Will Vincent
Highlights from a packed week in Kraków, where the standout theme was agentic AI workflows, with teams split between off-the-shelf tools and heavy internal tooling.
PyCon US 2026 Recap - Katherine Michel
Katherine's famous PyCon recap is here! Security and AI front and center, PSF and PyPI updates, steering council priorities for free-threading, and lots of great pictures.
Events
Django on the Med
Three days of Django development sprints, September 23-25 in Pescara, Italy. The second edition is free to attend and gathers Fellows, board and Steering Council members, and contributors new and experienced to push Django forward.
Django Day Copenhagen 2026
October 2 in Copenhagen. The first three talks are by Marijke Luttekes, Efe Öge, and Denny Biasiolli.
Django Job Board
Three new remote openings join the board this week, from AI-native full-stack work at Hive Collective to Django backend engineering for genetic testing at MyOme and Python + TypeScript roles at Fusionbox.
Senior Full Stack Engineer at Hive Collective 🆕
Senior Backend Engineer at MyOme 🆕
Python + TypeScript Engineers at Fusionbox 🆕
Freelance Full-Stack Web App Developer at Mindrift
Projects
adamchainz/django-crawl
An in-process site crawler using Django's test client.
FROWNINGdev/django-orm-lens
See your entire Django schema (every model, field, and relationship) in your editor, terminal, or AI agent, one keystroke away from a live ER diagram.
24 Jul 2026 3:00pm GMT
Django: release code words up to 6.1
Did you know that each Django release has a "code word" associated with it? It's hidden in plain sight, in the announcement blog post describing the list of features coming in the next version. I think this is a lovely little tradition.
I last covered the list back in 2021, for Django 3.2 (post). This post expands the table up until Django 6.1, which is expected next month (the first release candidate came out earlier this week).
Each code word links to its Wiktionary entry so you can see the definition. The word frequency column is based on the English data in the wordfreq Python package, as occurrences per billion words, so higher numbers mean the word is more common.
| Version | Post author | Quote with code word highlighted | Word frequency (per billion words) |
|---|---|---|---|
| 1.7 | James Bennett | ...will bring several major new features to Django, along with a host of other improvements... | 58,900 |
| 1.8 | Tim Graham | ...several major new features and a cornucopia of other improvements... | 363 |
| 1.9 | Tim Graham | ... myriad of goodies... | 3,090 |
| 1.10 | Tim Graham | ... panoply of new features... | 209 |
| 1.11 | Tim Graham | ... medley of new features... | 1,910 |
| 2.0 | Tim Graham | ... assortment of new features... | 1,820 |
| 2.1 | Tim Graham | ... smorgasbord of new features... | 263 |
| 2.2 | Carlton Gibson | ... salmagundi of new features... | 36 |
| 3.0 | Carlton Gibson | ... raft of new features... | 2,880 |
| 3.1 | Mariusz Felisiak | ... potpourri of new features... | 245 |
| 3.2 | Carlton Gibson | ... mezcla of new features... | N/A (not in English data) |
| 4.0 | Mariusz Felisiak | ... abundance of new features... | 7,240 |
| 4.1 | Carlton Gibson | ... profusion of new features... | 331 |
| 4.2 | Mariusz Felisiak | ... farrago of new features... | 83 |
| 5.0 | Natalia Bidart | ... deluge of exciting new features... | 1,120 |
| 5.1 | Natalia Bidart | ... kaleidoscope of improvements... | 692 |
| 5.2 | Sarah Boyce | ... composite of new features... | 8,710 |
| 6.0 | Natalia Bidart | ...assembles a mosaic of modern tools and thoughtful design... | 3,800 |
| 6.1 | Jacob Walls | ...a harmonious mélange of new features... | 98 |
Some observations on the newer entries:
- Carlton's mezcla (Django 3.2) wins on rarity, as it doesn't even appear in the English word frequency data.
- Sarah's composite (Django 5.2) was a wink at the composite primary key support added in that release.
- I ended my previous post of code words with the signoff "May your projects have their own mélange of new features". Perhaps this inspired Jacob to make the upcoming Django 6.1's code word mélange? I had better nail the signoff on this one then!
24 Jul 2026 4:00am GMT
22 Jul 2026
Django community aggregator: Community blog posts
Tracking Blips
bliptracker was a side project that I happened to produce during June and last week realised I hadn't written about it here, so here goes!
One annoyance I have with Claude.ai (or other web based LLM interfaces), is that I would start multiple conversations across multiple topics such as client work, organising my Todoist, an idea to explore, gifts to research, the list goes on, but I was keeping open tabs for each conversation to not lose track of the active conversations, but this didn't work as I still had those open loops in my head to follow up to move each conversation forwards.
I didn't want a full blown task manager (I pay for Todoist which fits perfectly), but I did want to track the state of each conversation in Claude from both the web app and the mobile. The result is a two fold solution, first there is a system prompt telling Claude to end each respond with either a 🔴, along with the next action required from me, or a ✅ which tells me the conversation is resolved. The second part of the solution is a Chrome extension which then automatically updates the title of any conversation with the red dot or green check mark, so I can tell at a glance which chats need work and which are done.
I do have a couple more features planned such as supporting other LLMs and a possible snooze feature. But for now it's a small working project that keeps my chats organised. It's available at bliptracker.xyz.
One final point on this project, I hope that eventually it gets replace by Anthropic building a better native product for tracking the status of chats, it's very limited right now. More widely this highlights that while new models are powerful and can do more, it still requires us as engineers to build products that solve actual problems in novel, tasteful and well designed solutions. That is what we pay for when buy a tool and what our users expect from us and something that no model as far as I can see will ever replace.
22 Jul 2026 5: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
09 Jun 2026
Planet Twisted
Hynek Schlawack: How to Ditch Codecov for Python Projects
Codecov's unreliability breaking CI on my open source projects has been a constant source of frustration for me for years. I have found a way to enforce coverage over a whole GitHub Actions build matrix that doesn't rely on third-party services.
09 Jun 2026 12:00am GMT
22 May 2026
Planet Twisted
Glyph Lefkowitz: Opaque Types in Python
Let's say you're writing a Python library.
In this library, you have some collection of state that represents "options" or "configuration" for a bunch of operations. Such a set of options is a bundle of potentially ever-increasing complexity. Thus, you will want it to have an extremely minimal compatibility surface, with a very carefully chosen public interface, that is either small, or perhaps nothing at all. Such an object conveys state and might have some private behavior, but all you want consumers to be able to do is build it in very constrained, specific ways, and then pass it along as a parameter to your own APIs.
By way of example, imagine that you're wrapping a library that handles shipping physical packages.
There are a zillion ways to do it ship a package. There are different carriers who can ship it for you. There's air freight, and ground freight, and sea freight. There's overnight shipping. There's the option to require a signature. There's package tracking and certified mail. Suffice it to say, lots of stuff.
If you are starting out to implement such a library, you might need an object called something like ShippingOptions that encapsulates some of this. At the core of your library you might have a function like this:
1 2 3 4 5 |
|
If you are starting out implementing such a library, you know that you're going to get the initial implementation of ShippingOptions wrong; or, at the very least, if not "wrong", then "incomplete". You should not want to commit to an expansive public API with a ton of different attributes until you really understand the problem domain pretty well.
Yet, ShippingOptions is absolutely vital to the rest of your library. You'll need to construct it and pass it to various methods like estimateShippingCost and shipPackage. So you're not going to want a ton of complexity and churn as you evolve it to be more complex.
Worse yet, this object has to hold a ton of state. It's got attributes, maybe even quite complex internal attributes that relate to different shipping services.
Right now, today, you need to add something so you can have "no rush", "standard" and "expedited" options. You can't just put off implementing that indefinitely until you can come up with the perfect shape. What to do?
The tool you want here is the opaque data type design pattern. C is lousy with such things (FILE, pthread_*_t, fd_set, etc). A typedef in a header file can easily achieve this.
But in Python, if you expose a dataclass - or any class, really - even if you keep all your fields private, the constructor is still, inherently, public. You can make it raise an exception or something, but your type checker still won't help your users; it'll still look like it's a normal class.
Luckily, Python typing provides a tool for this: typing.NewType.
Let's review our requirements:
- We need a type that our client code can use in its type annotations; it needs to be public.
- They need to be able to consruct it somehow, even if they shouldn't be able to see its attributes or its internal constructor arguments.
- To express high-level things (like "ship fast") that should stay supported as we add more nuanced and complex configurations in the future (like "ship with the fastest possible option provided by the lowest-cost carrier that supports signature verification").
In order to solve these problems respectively, we will use:
- a public
NewType, which gives us our public name... - which wraps a private class with entirely private attributes, to give us an actual data structure, while not exposing the constructor,
- a set of public constructor functions, which returns our
NewType.
When we put that all together, it looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
As a snapshot in time, this is not all that interesting; we could have just exposed _RealShipOpts as a public class and saved ourselves some time. The fact that this exposes a constructor that takes a string is not a big deal for the present moment. For an initial quick and dirty implementation, we can just do checks like if options._speed == "fast" in our shipping and estimation code.
However, the main thing we are doing here is preserving our flexibility to evolve the related APIs into the future, so let's see how we might do that. For example, let's allow the shipping options to contain a concrete and specific carrier and freight method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
As a NewType, our public ShippingOptions type doesn't have a constructor. Since _RealShipOpts is private, and all its attributes are private, we can completely remove the old versions.
Anything within our shipping library can still access the private variables on ShippingOptions; as a NewType, it's the same type as its base at runtime, so it presents minimal1 overhead.
Clients outside our shipping library can still call all of our public constructors: shipFast, shipNormal, and shipSlow all still work with the same (as far as calling code knows) signature and behavior.
If you need to build and convey some state within your public API, while avoiding breakages associated with compatibility churn, hopefully this technique can help you do that!
Acknowledgments
Thanks for reading, and 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.
-
The overhead is minimal, but it is not completely zero. The suggested idiom for converting to a
NewTypeis to call it like a function, as I've done in these examples, but if you are wanting to use this pattern inside of a hot loop, you can use# type: ignore[return-value]comments to avoid that small cost. ↩
22 May 2026 12:33am GMT
