skip to navigation
skip to content

Planet Python

Last update: July 29, 2026 07:48 PM UTC

July 29, 2026


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:

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:

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:

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.

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:

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:

PyTorch also provides key data-loading parameters to control how training data is processed:

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:

There are a few important implementation details to note when it comes to this section:

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:

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:

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.

Download PyCharm

About the author

Naa Ashiorkor

Naa Ashiorkor is a data scientist and tech community builder. She is deeply involved in the Python community and serves as an organizer for various conferences, including EuroPython. She is currently building PyLadies Tampere.

July 29, 2026 04:18 PM UTC

July 28, 2026


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 »

The PyCoder’s Weekly Logo


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

alt

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

Quiz: Tool-Agnostic Python Lock Files With pylock.toml

REAL PYTHON

Django 6.1 Release Candidate 1 Released

DJANGO SOFTWARE FOUNDATION

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

alt

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

Quiz: FastAPI: Python API Development With Light Speed

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

Quiz: Using NumPy reshape() to Change the Shape of an Array

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

Quiz: Exploring Python’s Built-in Functions

REAL PYTHON

Projects & Code

django-query-doctor: Diagnose Slow Django Queries

GITHUB.COM/HASSANZAIBHAY

ast-explore: Explore the AST of Your Python

GITHUB.COM/STEFMOLIN • Shared by Stefanie Molin

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 »

alt

[ 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 ]

July 28, 2026 07:30 PM UTC


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):

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:

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:

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:


Due to the accelerated nature of this grants funding round, we are holding supplemental PSF Grants Program Office Hours on the PSF Discord:

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

July 28, 2026 08:18 AM UTC


Python Bytes

#490 It’s a vibe coding party

<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://jvns.ca/blog/2026/07/21/more-nice-django-things/?featured_on=pythonbytes">Some more things about Django I've been enjoying</a></strong></li> <li><strong><a href="https://www.ft.com/content/cec8df9e-b43b-4cd1-8feb-c07e804e8d33?featured_on=pythonbytes">Who cleans up after the vibe-coding party</a>?</strong></li> <li><strong>Where Did All Your AI Tokens Go? <a href="https://github.com/kenn-io/agentsview?featured_on=pythonbytes">AgentsView</a> to the rescue!</strong></li> <li><strong>Careful with phishing all</strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=fVWWd7zvcTg' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="490">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href="https://training.talkpython.fm/?featured_on=pythonbytes"><strong>courses at Talk Python</strong></a></li> <li>Consulting from <a href="https://sixfeetup.com/?featured_on=pythonbytes"><strong>Six Feet Up</strong></a></li> </ul> <p><strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href="https://fosstodon.org/@mkennedy">Mastodon</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/mkennedy?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/mkennedy/?featured_on=pythonbytes">LinkedIn</a></li> <li>Calvin: <a href="https://sixfeetup.social/@calvin?featured_on=pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/calvinhp.com?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/calvinhp?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/calvinhp/?featured_on=pythonbytes">LinkedIn</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/pythonbytes.fm">BlueSky</a> / <a href="https://x.com/PythonBytes?featured_on=pythonbytes">X</a></li> </ul> <p>Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually <strong>Tuesday at 7am PT</strong>. Older video versions available there too.</p> <p>Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it.</p> <p><strong>Calvin #1: <a href="https://jvns.ca/blog/2026/07/21/more-nice-django-things/?featured_on=pythonbytes">Some more things about Django I've been enjoying</a></strong></p> <ul> <li><strong>Julia Evans</strong> is learning "2010-style" web dev (Django + SQL + server-rendered HTML) after years of Go backends and JS-heavy frontends</li> <li><strong>Query builders</strong>: likes defining custom QuerySet classes with chainable filter methods (<code>.approved().future().with_tags()</code>) — more readable than raw SQL</li> <li><strong>Template filters</strong>: highlights <code>urlize</code>, <code>linebreaksbr</code>, <code>json_script</code>, and especially <code>querystring</code> for building/modifying query-string links in templates</li> <li><strong>Migrations</strong>: still loves Django's auto-generated migrations — 19 and counting on her project</li> <li><strong>Skips inheritance</strong> for class-based views; prefers function-based views for sharing code, though fine using Django's own mixins/interfaces</li> <li><strong>Performance surprise</strong>: CPU profiling (via <code>py-spy</code>) — not slow DB queries — revealed the culprit; she'd accidentally disabled the cached template loader, and re-enabling it took throughput from ~2-3 req/s to ~12 req/s on a $10/mo VM</li> </ul> <p><strong>Michael #2: <a href="https://www.ft.com/content/cec8df9e-b43b-4cd1-8feb-c07e804e8d33?featured_on=pythonbytes">Who cleans up after the vibe-coding party</a>?</strong></p> <p>FT Magazine piece by Sam Learner (July 11) on AI coding tools overwhelming open source maintainers - sent in by listener Dylan McConnell, whose main point was that this ran in the <em>Financial Times</em>, not a dev blog.</p> <ul> <li><strong>cURL as the case study</strong> - Daniel Stenberg has been the only full-time person on it for years; libcurl has been installed an estimated 20+ billion times with 3,000+ listed contributors.</li> <li><strong>Bug bounty killed</strong> - cURL ended its paid security bounty program in January, citing an "explosion of AI slop reports" that take real time to debunk and drain morale.</li> <li><strong>Extractive contributions</strong> - authoring a PR is now nearly free, reviewing one still costs a human; tldraw's Steve Ruiz closed outside contributions entirely, asking why he'd want someone else writing the easy part.</li> <li><strong>Guido weighs in</strong> - van Rossum says projects are holding emergency meetings over the slop flow, and notes LLM patches tend to touch unrelated parts of a file, making review more tedious.</li> <li><strong>"Vibe Coding Kills Open Source"</strong> - paper from Miklós Koren's group: packages frequently recommended by coding models saw big download jumps with no matching engagement, breaking the reputation loop that sustains maintainers.</li> <li><strong>Stack Overflow flatlined</strong> - over 100,000 questions a month before ChatGPT, under 1,500 last month, with the response rate cut roughly in half; the public archive is now stale training data.</li> <li><strong>The course-creator angle</strong> - Josh Comeau's newest web dev course launched at about a third of prior enrollment, and he worries about devs who never learn which questions to ask.</li> </ul> <p>But <strong>the most interesting portion is what was omitted</strong>.</p> <ul> <li>Focused on: <a href="https://daniel.haxx.se/blog/2026/01/26/the-end-of-the-curl-bug-bounty/?featured_on=pythonbytes">The end of the curl bug-bounty</a></li> <li>Omitted: <a href="https://daniel.haxx.se/blog/2026/04/22/high-quality-chaos/?featured_on=pythonbytes">High-Quality Chaos</a></li> </ul> <p><strong>Why the omission is interesting</strong></p> <ul> <li>It fits a narrative. The FT piece is a maintenance-and-decline story, and January-Stenberg is a perfect witness for it. April-Stenberg complicates it - same person, same project, better data, opposite direction on the specific claim being used.</li> <li>The tell is already in the article. Learner quotes Stenberg saying AI tools are much better at finding problems than fixing them. That's the April thesis in one line, and it goes undeveloped.</li> <li>Reason for the shift is process, not vibes. Killing the bounty removed the cash incentive and the venue change filtered the rest. Worth saying out loud, because "AI reports got better" isn't quite it - "no bounty plus a real triage platform" is closer.</li> </ul> <p>Joke too: Sarah O’Connor <a href="https://blobs.pythonbytes.fm/recommended-on-ai-coding-takovers.jpeg?cache_id=0a8041">wrote a related piece</a> (is this just before skynet launches?)</p> <p><strong>Calvin #3: Where Did All Your AI Tokens Go? <a href="https://github.com/kenn-io/agentsview?featured_on=pythonbytes">AgentsView</a> to the rescue!</strong></p> <ul> <li>Local-first desktop/web app for browsing, searching, and analyzing your past AI coding agent sessions (Claude Code, Codex, Copilot, Cursor, Gemini, Aider, and dozens more)</li> <li>Auto-discovers session files on your machine — no config needed; everything stored locally in SQLite, no cloud/accounts</li> <li><code>agentsview usage</code> is a drop-in <code>ccusage</code> alternative — reads from pre-indexed SQLite, reports run 80–220× faster on large histories</li> <li>New <strong>Activity</strong> dashboard shows peak concurrency, active vs. idle time, agent-minutes, and cost — filterable by project/agent/machine, with a <code>-json</code> CLI report too</li> <li>Full-text + optional semantic search across every session; also imports <a href="http://Claude.ai/ChatGPT?featured_on=pythonbytes">Claude.ai/ChatGPT</a> chat exports</li> <li>Install via <code>pip install agentsview</code>, <code>uvx agentsview</code>, <code>brew install --cask agentsview</code>, or download desktop binaries from GitHub Releases</li> </ul> <p><strong>Michael #4: Careful with phishing all</strong></p> <p><strong>The situation</strong></p> <p>I pass this along because it was a pretty sneaky bit of targeted phishing, and happened to play off an old interaction in bandit's repo. As usual with phishing scams there are a bunch of tells that this isn't legitimate, but just enough plausibility that I could see falling for it in a weak moment. Relative nobodies like me haven't historically been worth the effort to hit with scams this specific. Agents change the game though :-/. Be careful out there folks!</p> <p><strong>Original message</strong></p> <p>From: "Patrick (Blacktrace)" [HTML_REMOVED] To: LISTENER EMAIL Subject: Your Bandit #1350 (B105 NextToken false positive) -- just fixed that exact case</p> <p>Date: Wednesday, July 15, 2026 12:02 AM</p> <p>Hi AJ,</p> <p>Saw your Bandit issue #1350 -- the B105 hardcoded-password false positive on the string NextToken. I build a deterministic gate that filters that class of Bandit noise, and #1350 was literally the case I just fixed: NextToken / next_token / page_token / nextPageToken now stay quiet, while a genuine hardcoded token like api_token="sk-live-..." still fires. Verified against your exact case.</p> <p>30-second paste: https://blacktrace.co/noise-eraser</p> <p>Where it still trips, published: https://blacktrace.co/kruc</p> <p>Curious whether it clears what you hit -- and if it trips on something of yours, that's the more useful reply.</p> <ul> <li>Patrick, Blacktrace</li> </ul> <hr /> <p>I asked Claude for some analysis too. It was pretty good at finding them.</p> <p>The message name-drops enough real detail to feel legit, but the structure is pure phishing - everything in it exists to get AJ onto <a href="http://blacktrace.co?featured_on=pythonbytes">blacktrace.co</a>. The strongest ones:</p> <ul> <li><strong>Freemail sender, corporate signoff.</strong> Signs as "Patrick, Blacktrace" but sends from <a href="mailto:emailpjv@gmail.com">emailpjv@gmail.com</a>. Real company outreach comes from the company domain, not a personal Gmail - and there's no last name.</li> <li><strong>Over-specific targeting.</strong> It mirrors AJ's exact public activity - issue #1350, the B105 rule, the NextToken false positive, even the token variants. That's the "just enough plausibility" AJ flagged, and it's exactly what agents make cheap: scrape a GitHub issue, auto-generate tailored bait. Legit cold outreach rarely reads your history back to you this precisely.</li> <li><strong>The entire payload is two links.</strong> Strip the technical flattery and the message is just "paste here" plus "see results here." When the whole point of an email is the click, that's the tell.</li> <li><strong>"30-second paste."</strong> Low-friction urgency, and "paste" most likely means paste your source into their tool - handing your code to a stranger's site. Exfiltration dressed as convenience.</li> <li><strong>Brand-new, no-reputation domain.</strong> <a href="http://blacktrace.co?featured_on=pythonbytes">blacktrace.co</a> has no track record, and the name is doing some ominous work. The <code>/kruc</code> slug is random noise, not how real product pages get named.</li> <li><strong>Precise-sounding jargon that's actually vague.</strong> "Deterministic gate," "noise-eraser" - impressive, empty. Bolted onto correct real details (B105 is the Bandit hardcoded-password test, <code>sk-live-</code> is a Stripe live-key prefix) to borrow credibility.</li> <li><strong>The disarming close.</strong> "if it trips on something of yours, that's the more useful reply" - engineered humility that flatters your expertise and baits a response. Makes engaging feel like you're doing <em>them</em> a favor, which drops your guard.</li> </ul> <p><strong>Extras</strong></p> <p>Calvin:</p> <ul> <li><a href="https://2026.djangocon.us/?featured_on=pythonbytes">DjangoCon US 2026</a> is rapidly approaching, <strong>August 24-28, Chicago</strong></li> <li><strong>Ruff v0.16.0 massively expands its default rule set</strong> <ul> <li>Ruff now enables 413 rules by default, up from 59</li> <li>https://astral.sh/blog/ruff-v0.16.0</li> </ul></li> </ul> <p>Michael:</p> <ul> <li>Completely <a href="https://pythonbytes.fm">redesigned the home page</a>.</li> <li>Try /insights in Claude Code (terminal)</li> </ul> <p><strong>Joke:</strong> <a href="https://x.com/pr0grammerhum0r/status/2013325616951517233?s=46&featured_on=pythonbytes">We’re Safe</a></p>

July 28, 2026 08:00 AM UTC

July 27, 2026


Python Morsels

Running subprocesses in Python

You can use Python's subprocess.run function to launch other programs from within Python.

Table of contents

  1. Launching external programs from Python
  2. Subprocess, defined
  3. Launch a subprocess with subprocess.run
  4. Using subprocess.run with a list or a string
  5. Capturing the output of a subprocess
  6. Automatically decoding subprocess output
  7. Handling errors in subprocesses
  8. Raising exceptions based on subprocess exit codes
  9. Helper functions for subprocess.run
  10. Launch subprocesses with subprocess.run

Launching external programs from Python

We're going to focus on starting up other processes (that may not be Python processes), communicating with those processes, and handling their output.

We're not talking about the related topics of concurrency and parallelism. For those, you can use Python's threading, multiprocessing, or concurrent.futures modules.

We're specifically going to focus on spawning subprocesses. For this we will use Python's subprocess module.

Subprocess, defined

A subprocess is a process …

Read the full article: https://www.pythonmorsels.com/running-subprocesses-in-python/

July 27, 2026 01:20 PM UTC


Python Software Foundation

Announcing Python Software Foundation Fellow Members for Q2 2026! 🎉

The PSF is pleased to announce its second batch of PSF Fellows for 2026. Let us welcome the new PSF Fellows for Q2! The following people continue to do amazing things for the Python community:

Andy Terrel

Blog

Julius Nana Acheampong Boakye

LinkedIn, X, GitHub, Website

Petr Viktorin

GitHub

Sayantika Banik

GitHub, LinkedIn, Bluesky

Takanori Suzuki

GitHub, Website, LinkedIn, X, Untappd

Thank you for your continued contributions. We have added you to our Fellows Roster.

The above members help support the Python ecosystem by being phenomenal leaders, sustaining the growth of the Python scientific community, maintaining virtual Python communities, maintaining Python libraries, creating educational material, organizing Python events and conferences, starting Python communities in local regions, and overall being great mentors in our community. Each of them continues to help make Python more accessible around the world. To learn more about the new Fellow members, check out their links above.

Let's continue recognizing Pythonistas all over the world for their impact on our community. The criteria for Fellow members is available on our PSF Fellow Membership page. If you would like to nominate someone to be a PSF Fellow, please send a description of their Python accomplishments and their email address to psf-fellow at python.org. We are accepting nominations for Quarter 3 of 2026 through August 20th, 2026.

Are you a PSF Fellow and want to help the Work Group review nominations? Contact us at psf-fellow at python.org.

July 27, 2026 12:48 PM UTC

July 26, 2026


Talk Python to Me

#556: Updates on Django's Async Story

For years, "Django and async" came with an asterisk. The docs themselves warned you off it. Scary performance notes, a story that felt half-finished. Well, that story just got rewritten, literally, and the person who rewrote it is here to tell you why the old framing was wrong. <br/> <br/> Carlton Gibson is a former Django Fellow, sat on the security team for eight years, and he's on the steering council. On this episode we get into the async topic doc rewrite, what actually remains versus what was just fear, the new Tasks framework in 6.0, DB-level cascades and fetch modes landing in 6.1, and why free-threading is the bet that's about to pay off big for Django. If you've been told Django's async story isn't ready, this is the episode that puts that myth to bed.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/sentry'>Sentry Error Monitoring, Code talkpython26</a><br> <a href='https://talkpython.fm/devopsbook'>Python in Production</a><br> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br/> <br/> <h2 class="links-heading mb-4">Links from the show</h2> <div><strong>DjangoCon Europe</strong>: <a href="https://djangocon.eu/?featured_on=talkpython" target="_blank" >djangocon.eu</a><br/> <strong>PyCon Italia</strong>: <a href="https://pycon.it/?featured_on=talkpython" target="_blank" >pycon.it</a><br/> <strong>Django on the Med</strong>: <a href="https://djangomed.eu/?featured_on=talkpython" target="_blank" >djangomed.eu</a><br/> <strong>Django Mantle</strong>: <a href="https://noumenal.es/mantle/?featured_on=talkpython" target="_blank" >noumenal.es</a><br/> <strong>PyPI</strong>: <a href="https://pypi.org/project/django-mantle/?featured_on=talkpython" target="_blank" >pypi.org</a><br/> <strong>release notes</strong>: <a href="https://docs.djangoproject.com/en/6.1/releases/6.1/?featured_on=talkpython" target="_blank" >docs.djangoproject.com</a><br/> <strong>on_delete</strong>: <a href="https://docs.djangoproject.com/en/6.1/ref/models/fields/#django.db.models.ForeignKey.on_delete" target="_blank" >docs.djangoproject.com</a><br/> <strong>Fetch modes</strong>: <a href="https://docs.djangoproject.com/en/6.1/topics/db/fetch-modes/?featured_on=talkpython" target="_blank" >docs.djangoproject.com</a><br/> <strong>HttpRequest.multipart_parser_class</strong>: <a href="https://docs.djangoproject.com/en/6.1/ref/request-response/?featured_on=talkpython" target="_blank" >docs.djangoproject.com</a><br/> <strong>async topic doc</strong>: <a href="https://docs.djangoproject.com/en/dev/topics/async/?featured_on=talkpython" target="_blank" >docs.djangoproject.com</a><br/> <strong>docs</strong>: <a href="https://docs.djangoproject.com/en/6.0/topics/tasks/?featured_on=talkpython" target="_blank" >docs.djangoproject.com</a><br/> <strong>DEP 14</strong>: <a href="https://github.com/django/deps/blob/main/final/0014-background-workers.rst?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>django-tasks</strong>: <a href="https://github.com/RealOrangeOne/django-tasks?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>django-tasks-local</strong>: <a href="https://github.com/lincolnloop/django-tasks-local?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Celery</strong>: <a href="https://docs.celeryq.dev/?featured_on=talkpython" target="_blank" >docs.celeryq.dev</a><br/> <strong>PEP 703</strong>: <a href="https://peps.python.org/pep-0703/?featured_on=talkpython" target="_blank" >peps.python.org</a><br/> <strong>free-threading HOWTO</strong>: <a href="https://docs.python.org/3/howto/free-threading-python.html?featured_on=talkpython" target="_blank" >docs.python.org</a><br/> <strong>PEP 779</strong>: <a href="https://peps.python.org/pep-0779/?featured_on=talkpython" target="_blank" >peps.python.org</a><br/> <strong>ASGI</strong>: <a href="https://docs.djangoproject.com/en/6.1/howto/deployment/asgi/?featured_on=talkpython" target="_blank" >docs.djangoproject.com</a><br/> <strong>PGBouncer</strong>: <a href="https://www.pgbouncer.org/?featured_on=talkpython" target="_blank" >www.pgbouncer.org</a><br/> <strong>Channels</strong>: <a href="https://channels.readthedocs.io/?featured_on=talkpython" target="_blank" >channels.readthedocs.io</a><br/> <strong>sync_to_async / async_to_sync</strong>: <a href="https://docs.djangoproject.com/en/6.1/topics/async/#async-adapter-functions" target="_blank" >docs.djangoproject.com</a><br/> <strong>noumenal.es</strong>: <a href="https://noumenal.es/?featured_on=talkpython" target="_blank" >noumenal.es</a><br/> <strong>Django Chat</strong>: <a href="https://djangochat.com/?featured_on=talkpython" target="_blank" >djangochat.com</a><br/> <strong>@carlton@fosstodon.org</strong>: <a href="https://fosstodon.org/@carlton" target="_blank" >fosstodon.org</a><br/> <strong>Article: Cutting Python Web App Memory Over 31%</strong>: <a href="https://mkennedy.codes/posts/cutting-python-web-app-memory-over-31-percent/?featured_on=talkpython" target="_blank" >mkennedy.codes</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=J1WXYE1Wjzo" target="_blank" >youtube.com</a><br/> <strong>Episode #556 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/556/updates-on-djangos-async-story#takeaways-anchor" target="_blank" >talkpython.fm/556</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/556/updates-on-djangos-async-story" target="_blank" >talkpython.fm</a><br/> <br/> <strong>Theme Song: Developer Rap</strong><br/> <strong>🥁 Served in a Flask 🎸</strong>: <a href="https://talkpython.fm/flasksong" target="_blank" >talkpython.fm/flasksong</a><br/> <br/> <strong>---== Don't be a stranger ==---</strong><br/> <strong>YouTube</strong>: <a href="https://talkpython.fm/youtube" target="_blank" ><i class="fa-brands fa-youtube"></i> youtube.com/@talkpython</a><br/> <br/> <strong>Bluesky</strong>: <a href="https://bsky.app/profile/talkpython.fm" target="_blank" >@talkpython.fm</a><br/> <strong>Mastodon</strong>: <a href="https://fosstodon.org/web/@talkpython" target="_blank" ><i class="fa-brands fa-mastodon"></i> @talkpython@fosstodon.org</a><br/> <strong>X.com</strong>: <a href="https://x.com/talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @talkpython</a><br/> <br/> <strong>Michael on Bluesky</strong>: <a href="https://bsky.app/profile/mkennedy.codes?featured_on=talkpython" target="_blank" >@mkennedy.codes</a><br/> <strong>Michael on Mastodon</strong>: <a href="https://fosstodon.org/web/@mkennedy" target="_blank" ><i class="fa-brands fa-mastodon"></i> @mkennedy@fosstodon.org</a><br/> <strong>Michael on X.com</strong>: <a href="https://x.com/mkennedy?featured_on=talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @mkennedy</a><br/></div>

July 26, 2026 04:49 PM UTC


Ned Batchelder

Acidica

My latest fun project is a BASIC interpreter called Acidica. Classic BASIC is an old-school language first developed in 1964 that saw an explosion of implementations on microcomputers in the ‘70s and ‘80s. It’s much more primitive than the Visual Basic that you might be familiar with.

A simple BASIC program:

10 INPUT "What is your name"; U$

20 PRINT "Hello "; U$
30 INPUT "How many stars do you want"; N
40 S$ = ""
50 FOR I = 1 TO N
60 S$ = S$ + "*"
70 NEXT I
80 PRINT S$
90 INPUT "Do you want more stars"; A$
100 IF LEN(A$) = 0 THEN 90
110 A$ = LEFT$(A$, 1)
120 IF A$ = "Y" OR A$ = "y" THEN 30
130 PRINT "Goodbye ";U$
140 END

Run it, and you get this:

What is your name? Ned

Hello Ned
How many stars do you want? 10
**********
Do you want more stars? y
How many stars do you want? 20
********************
Do you want more stars? n
Goodbye Ned

The wide variety of BASIC flavors meant I first had to decide what to implement. I found Vintage BASIC and used its spec, both because it is concisely described, and because it has an implementation I could run to double-check behavior when I had questions. The site also has a collection of runnable games from Creative Computing magazine, which I remember fondly.

This was a perfect vacation-week project. It has no real-world consequences. It had some interesting problems to puzzle through. It was testable. It satisfied some nostalgia for my earlier computing days. It was bounded enough to be “done”.

In those ways, it’s very similar to a vacation project of mine from four years ago: Stilted, an implementation of PostScript.

Acidica is not useful for writing new programs, only because BASIC itself is so difficult. There is no scoping beyond single-line functions, variables names can be as long as you want but only the first two letters and first digit are significant. Keywords are recognized anywhere, so FACTOR can’t be variable name because it has TO in the middle. The only control structures are FOR, IF, and GOTO. It’s something of a testament to human persistence that programs like three-dimensional tic-tac-toe can be written in it.

As a side project, I could choose my development style: no real type checking (partly because BASIC’s values would be awkward to squeeze into static typing), and very few docstrings. There are lots of tests, but only integration tests: every test is a BASIC program to run, with a check for the correct output and/or the expected error.

To be honest, the “only integration tests” approach was kind of a pain, but I stuck with it and resisted the temptation to add unit tests along the way.

Another choice I made: no AI. I like writing programs. I get a deeper sense of the thing I am building when I have my fingers in the clay. Since there was no deadline, or even any reason to ever finish the project, I could take my time and not be rushed.

But I like the result. I enjoyed the time I spent working on it. I liked being able to stop and devote pure thinking time while doing other things when I got to the next hurdle. The next steps here might be to use this project as a test bed for some development ideas. Or maybe add a BASIC-to-Python transpiler. Or maybe it’s done.

July 26, 2026 02:54 PM UTC


EuroPython Society

EuroPython 2026 Code of Conduct Transparency Report

The 2026 edition of the EuroPython conference took place both online and in person in July 2026. This was the fourth conference under our current Code of Conduct (CoC), and we had Code of Conduct working group members continuously available both online and in person.

Reports

Over the course of the conference, the Code of Conduct team was made aware of the following issues:

We thank everyone who reported concerns during the conference.

July 26, 2026 08:40 AM UTC

July 24, 2026


Peter Bengtsson

Claude Opus is 10x faster than OpenAI GPT 5 at non-streaming completions

Claude is much faster than OpenAI gpt-5 and also faster than gpt-5-mini

July 24, 2026 03:48 PM UTC


Django Weblog

See You in Chicago in One Month!

In just one month, developers, maintainers, educators, and Django enthusiasts from around the world will gather in Chicago for DjangoCon US 2026.

DjangoCon US is more than a conference, it's a place to learn from the community, share ideas, contribute to Django, and make connections that last long after the event ends. Whether you're attending your first DjangoCon US or you've been coming for years, we're excited to welcome you.

Over five days, attendees will have the opportunity to:

If you're planning to join us, tickets are still available, and there's still time to reserve your stay at the conference hotel. The hotel room block deadline is August 3, so be sure to book your room before then.

Register today!

August 24–28, 2026
Chicago, Illinois

We can't wait to see you in Chicago!

July 24, 2026 11:00 AM UTC


Armin Ronacher

Codeberg Divides

Codeberg recently changed its terms to exclude projects that are largely written with generative AI. Since I want GitHub to face competition I have thoughts.

Codeberg is entirely within its rights to do this. It is an association with members and a democratic process, and that process produced a result. But democracy is a way of making a decision, not a guarantee that the decision is inclusive, wise, or even good for the people already depending on it. A majority can still decide that certain projects and people no longer belong.

GitHub’s governance has never been democratic and there is plenty about the platform that I dislike. Yet democracy is not the main property I need from infrastructure. I need it to be predictable, dependable, and reasonably neutral towards the legal Open Source software hosted on it. A democratic provider without a clear constitution can be worse at those things than a corporation.

The actual wording makes this more difficult. The terms prohibit projects that mostly consist of code written by generative AI tools. In an actively developed codebase, what does “mostly” mean, and who can still tell? I could not reliably assign authorship percentages to many of my own recent projects. The line is open to interpretation precisely where it needs to be enforceable. In practice the center will probably lose out, as it has a bias.

A harsher line would probably be preferable. If Codeberg wants no LLM involvement, it should say so. If it wants to prevent autonomous repository spam and abusive resource consumption, it should write rules for those instead. The current middle ground delegates too much of the policy to moderators and community norms. I’m currently assuming the community around it draws a much harsher social boundary, making projects and maintainers unwelcome even when they technically comply.

It is a real shame that the Open Source and Free Software communities are splitting this deeply over LLMs and agents. There are serious questions about copyright, labor, energy use, slop, and maintainers drowning in generated contributions. But these tools are also becoming part of how software is made. The Open Source world needs to figure out how to engage with that future, not just divide into camps. More importantly, LLMs if done and used well, should be welcome to all of us. They could be used to reclaim control and power, away from large corporations and institutions.

As I mentioned before, I want GitHub to face true competition in the Open Source space. I would particularly like some of it to come from associations rather than another large corporation. As a European project, Codeberg naturally matters to me even more. It can choose to be a smaller community with a stronger political identity, but that is a different ambition from being a broad and dependable European alternative to GitHub.

I wish Codeberg were more forward-looking here: willing to host the Open Source software of tomorrow, not only software made in the ways its community approves of today. It has every right to make the choice it made, but I just do not think it is a good one.

July 24, 2026 12:00 AM UTC

July 23, 2026


Python Software Foundation

Get Ready: PSF Board Nominations Opening Soon!

Who runs for the PSF Board? People who care about the Python community, who want to see it flourish and grow, and also have a few hours a month to attend regular meetings, serve on committees, participate in conversations, and promote the Python community. We're looking for candidates with a diverse range of skills and backgrounds, including leadership experience, fundraising knowledge, non-profit familiarity, and event organizing. Technical expertise, a record of collaboration, and experience speaking or teaching in the Python community are also all qualities we hope to see in Board members.

Want to learn more about being on the PSF Board? Check out the following resources to learn more about the PSF, as well as what being a part of the PSF Board entails:

Board Election Timeline

Not sure what UTC is for you locally? Check this UTC time converter!

Nominations 

You can nominate yourself or someone else. If you're nominating someone else, we'd encourage you to reach out to them first to make sure they're excited about the opportunity and give them a heads up that they'll need to submit their own nomination statement via the nomination form. Take a look at last year’s nomination statements for reference. 

To submit a nomination for yourself or someone else, use the 2026 PSF Board Election Nomination Form on our website. The form will open on Tuesday, July 28th, 2:00 pm UTC and close on Tuesday, August 11th, 2:00 pm UTC. 

To support potential candidates and nominators, the PSF has created a nomination resource (embedded below). It includes tips, formatting instructions, and guidance on what to include in a nomination. The goal is to help nominees understand what to expect and ensure that all candidates are provided the same clear and consistent standards.


Nominee Election Participation

PSF Board nominees will be invited to participate in the PSF Board Office Hour on the PSF Discord on September 8th at 1PM UTC. PSF Board Office Hours are a chance for the Python community to ask questions, share perspectives, and in this case, connect with PSF Board nominees. If you are unable to attend the sessions for whatever reason, that’s totally fine, though we’d love to have each of you participate!

PSF Board nominees will also be invited to participate in text-based interviews that will result in content published on the PSF Blog. The interview questions will be similar to those used in the video interviews that have been produced in years past:

A current PSF Board member will reach out to you with instructions and field any questions you may have about the interviews. We ask that nominees keep an eye on their email inboxes during the nomination period and right after so that we can ensure your interview responses get published for the Python community’s consideration. 

Voting Affirmation Reminder

Every PSF Voting Member (Supporting, Contributing, and Fellow) must affirm their intention to vote no later than Tuesday, August 25th, 2:00 pm UTC, to participate in this year’s election. You should have received an email from "psf@psfmember.org <Python Software Foundation>" with the subject "[Action Required] Affirm your PSF Membership voting intention for 2026 PSF Board Election" that contains information on how to affirm your voting status. 

You can see your membership record and status on your PSF Member User Information page. If you are a voting-eligible member and do not already have a login, please create an account on psfmember.org first and then email psf-elections@pyfound.org so we can link your membership to your account. 

July 23, 2026 05:16 PM UTC

Get Ready: Python Packaging Council Nominations Opening Soon!

The inaugural Python Packaging Council Election nomination period opens next week on Tuesday, July 28th, 2:00 pm UTC and closes on Tuesday, August 11th, 2:00 pm UTC.

The Python Packaging Council (PPC) will be the technical decision-making body for the interoperability specifications that govern how Python packages are built, distributed, and installed. It will also coordinate efforts among packaging tool maintainers, the Python core team, and the broader community.

Running for the Packaging Council

Do you have a vision for improving the Python packaging experience? Do you make the tools used to build and consume Python packages? Are you passionate about building communities, consensus, and standards focused on the user experience? If these resonate with you, and you have the time to attend regular meetings and participate in the standardization process, you should consider running for the inaugural PPC!

We're looking for candidates who can build bridges between projects and communities, who enjoy working with a very large community of passionate volunteers, and have a willingness to represent the wider community ahead of any single tool, project, or employer. We also welcome candidates who have a diverse set of skills and experiences, including open-governance experience, community stewardship, fundraising knowledge, and (of course!) technical expertise in Python packaging and distribution.

PEP 772 does provide non-binding operational suggestions, which hint at how the council could function. As this is the inaugural PPC, the individuals serving on it will be establishing the initial operating procedures, scope, interests, and agenda that future councils will build upon. Notably, "establishing specific processes for [the] Packaging Council and PyPA relationship" is something that the inaugural Packaging Council is expected to do.

Election Overview

The 2026 inaugural election fills all five seats on the PPC. The two candidates receiving the highest number of votes shall be designated Cohort A with a two year term, and the three candidates receiving the next highest number of votes shall be designated Cohort B with a one year term.

In future elections, each cohort will be elected for a full two-year term in alternating years, so that roughly half of the PPC turns over each cycle.

Election Timeline

Not sure what UTC is for you locally? Check this UTC time converter!

Nomination details

You can nominate yourself or someone else. If you're nominating someone else, we'd encourage you to reach out to them first to make sure they're excited about the opportunity and give them a heads up that they'll need to submit their own nomination statement too. Remember, nominees must themselves be PSF voting members, and nomination statements must include information about the nominee’s relevant affiliations.

To submit a nomination for yourself or someone else, use the 2026 PPC Election Nomination Form on our website. The form will open on Tuesday, July 29th, 2:00 pm UTC and close on Tuesday, August 12th, 2:00 pm UTC.

Voting Reminder!

Every PSF Voting Member (Supporting, Contributing, and Fellow) needs to be a member in good standing by August 25th and affirm their membership to vote in this election. You should have received an email with information on how to affirm your voting status.

You can see your membership record and status on your PSF Member User Information page. If you are a voting-eligible member and do not already have a login, please create an account on psfmember.org first and then email pc-elections@python.org so we can link your membership to your account.

July 23, 2026 12:18 PM UTC


Python Insider

Get Ready: 2026 Python Packaging Council Nominations Opening Soon!

The inaugural Python Packaging Council election nomination period opens on Tuesday, July 28th, 2:00 pm UTC and closes on Tuesday, August 11th, 2:00 pm UTC.

July 23, 2026 12:00 AM UTC

July 22, 2026


Django Weblog

Django 6.1 release candidate 1 released

Django 6.1 release candidate 1 is now available. It represents the final opportunity for you to try out the version that offers a harmonious mélange of new features and usability improvements, before Django 6.1 final is released.

The release candidate stage marks the string freeze and the call for translators to submit translations. Provided no major bugs are discovered that can't be solved in the next two weeks, Django 6.1 will be released on or around August 5. Any delays will be communicated on the Django forum.

Please use this opportunity to help find and fix bugs (which should be reported to the issue tracker), you can grab a copy of the release candidate package from our downloads page or on PyPI.

The PGP key ID used for this release is Jacob Walls: 131403F4D16D8DC7

July 22, 2026 08:00 PM UTC


Python Software Foundation

The PSF D&I Workgroup is Starting Office Hours in July!




Starting Tuesday 28 July, 2026, the PSF Diversity & Inclusion (D&I) Workgroup is opening its virtual doors once a month on Discord. Come chat with workgroup members from all over the world!

Doing diversity and inclusion work in tech can feel isolating sometimes. You might be organizing a meetup, writing a code of conduct, trying to get funding for your community, or helping people feel welcome, often in your spare time, and wondering if anyone else is wrestling with the same things.

They are. We are! And we would love to get all of us in the same room.

This July, the PSF D&I Workgroup will be hosting monthly office hours within Discord. These will be open, text-based conversations where we encourage you to ask questions, sha
re what you are working on, and connect with other people who care about making the Python community more welcoming.

The details

The PSF D&I Office Hours will be on the last Tuesday of every month. Because our community is spread across the globe, we will alternate between two times so we can cover as many time zones as possible:

  • 1 PM UTC / 9 AM US Eastern

  • 9 PM UTC / 5 PM US Eastern

Our first session will be on Tuesday, 28 July 2026 at 1 PM UTC. Here is roughly what that looks like around the world:

Region

Local time on 28 July

US Pacific, Los Angeles – (UTC-7h)

6:00 AM

US Eastern, New York – (UTC-4h)

9:00 AM

Brazil, São Paulo – (UTC-3h)

10:00 AM

UTC

1:00 PM

West Africa, Lagos – (UTC+1h)

2:00 PM

Central Europe, Amsterdam / Berlin / Madrid – (UTC+2h)

3:00 PM

East Africa, Nairobi – (UTC+3h)

4:00 PM

Iran, Tehran – (UTC+3:30h)

4:30 PM

India, New Delhi – (UTC+5:30h)

6:30 PM

China, Beijing – (UTC+8h)

9:00 PM

Japan, Tokyo – (UTC+9h)

10:00 PM

Australia, Sydney – (UTC+10h)

11:00 PM

If 6 AM in Los Angeles or 11 PM in Sydney made you wince, do not worry. The August session will be at 9 PM UTC, and we will keep alternating from there.

You will find us in the #psf-diversity channel on the PSF Discord. If you’re new to Discord, check out some Discord Basics to help you get started. 

What will we talk about

Honestly? Whatever is on your mind related to Python, your communities, and D&I.

Since our workgroup exists to advise the PSF on diversity and inclusion, some conversations we are especially hoping to have include:

  • Ideas for policies, initiatives, and grant proposals to diversify the PSF missions. Feedback from the community about these topics will help the PSF D&I Workgroup provide recommendations to the PSF Board of Directors.

  • Your feedback, plain and simple. We want to understand how the PSF can better serve and grow a diverse membership, and we cannot do that without hearing from the community itself.

  • How things are actually going. Part of our job is measuring and sharing the PSF’s progress on its diversity initiatives, and we would rather do that in conversation with you than in a report nobody reads. We also want to understand and learn about the current state of Python communities around the world.

No camera, no mic, no pressure

Office hours are text chat only.

Show up in your pajamas, join from the bus, lurk quietly for the first twenty minutes. It is all fine.

And if you cannot make it at all, the conversation stays in the channel, so you can catch up later when it suits you. If something in the chat sparks a thought you would like to share with us directly, you are always welcome to email the workgroup at diversity-inclusion-wg@python.org.

Bring your own language

Because we are the D&I Workgroup, our members come from around the world! Alongside the main conversation, we will open threads in other languages where possible. Depending on the presence of our members, we would be happy to chat in Spanish, Portuguese, Chinese, Hindi, French or even Persian! Let us know during the office hour if you have a specific language you hope to converse in, or jump in with whichever language thread feels like home.

See you on the 28th!

The first office hour session is on Tuesday, 28 July 2026 at 1 PM UTC, in #psf-diversity on Discord.

Come say hi, even if it is just to tell us what you are working on with Python. We are really looking forward to meeting you!





July 22, 2026 09:46 AM UTC


Python GUIs

Constantly Print Subprocess Output While Process is Running — How to stream live output from a subprocess into your PyQt6 GUI without freezing the interface

I need to call a legacy Bash program and display the results in a Qt window. The problem is the subprocess doesn't return each output line as it happens — it waits until the entire command is finished, then dumps everything to the window at once. If the command takes a long time, the user thinks the system is frozen. How can I get live, line-by-line output from a subprocess into my Qt application?

If you've ever launched a long-running external command from a PyQt6 application and watched your entire GUI freeze until it finishes, you've hit one of the most common pitfalls in Python GUI development: blocking the event loop.

When you call subprocess.run(), Python stops and waits for the process to complete before moving on. While it's waiting, Qt's event loop — the mechanism responsible for redrawing the window, responding to clicks, and processing signals — is completely stalled. That means that the UI will not update.

There are two approaches to stream subprocess output in real time in PyQt6:

  1. Use QProcess, which is Qt's built-in way to run external programs. It integrates directly with the event loop and emits signals as output becomes available.
  2. Use a background QThread with Python's subprocess.Popen to read output line by line and send it back to the GUI via signals.

The wrong approach

First, let's see what happens when you use subprocess and block the event loop.

python
import subprocess
import sys

from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPlainTextEdit,
    QPushButton, QVBoxLayout, QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Subprocess Demo - Blocking")

        self.text_area = QPlainTextEdit()
        self.text_area.setReadOnly(True)

        self.button = QPushButton("Run Command")
        self.button.clicked.connect(self.run_command)

        layout = QVBoxLayout()
        layout.addWidget(self.text_area)
        layout.addWidget(self.button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def run_command(self):
        # This blocks the entire GUI until the command finishes!
        result = subprocess.run(
            ["bash", "-c", "for i in 1 2 3 4 5; do echo Line $i; sleep 1; done"],
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
        )
        self.text_area.setPlainText(result.stdout.decode())


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

Click the button, and the window becomes unresponsive for five seconds. Then all the output appears at once. The GUI didn't update during that time because subprocess.run() blocked the Qt event loop until it was finished.

Now, let's look at the two solutions to this problem:

Streaming Subprocess Output with QProcess

QProcess is Qt's own class for running external programs asynchronously. It starts the process and returns immediately, letting the event loop continue. As the external program produces output, QProcess emits the readyReadStandardOutput signal, which you can connect to a slot that reads and displays the new data.

This is the most "Qt-native" solution for displaying real-time subprocess output in PyQt6 and works well for many use cases. For a deeper dive into QProcess including handling stdin, managing multiple processes, and parsing output, see the complete QProcess tutorial.

python
import sys

from PyQt6.QtCore import QProcess
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPlainTextEdit,
    QPushButton, QVBoxLayout, QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QProcess Live Output")
        self.process = None

        self.text_area = QPlainTextEdit()
        self.text_area.setReadOnly(True)

        self.button = QPushButton("Run Command")
        self.button.clicked.connect(self.run_command)

        layout = QVBoxLayout()
        layout.addWidget(self.text_area)
        layout.addWidget(self.button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def run_command(self):
        if self.process is not None:
            return  # Already running

        self.text_area.clear()
        self.button.setEnabled(False)

        self.process = QProcess(self)
        self.process.readyReadStandardOutput.connect(self.handle_stdout)
        self.process.readyReadStandardError.connect(self.handle_stderr)
        self.process.finished.connect(self.process_finished)

        # QProcess takes the program and arguments separately.
        # To run a bash command, pass "-c" and the command string as arguments.
        self.process.start(
            "bash",
            ["-c", "for i in 1 2 3 4 5; do echo \"Line $i\"; sleep 1; done"],
        )

    def handle_stdout(self):
        data = self.process.readAllStandardOutput()
        text = bytes(data).decode("utf-8")
        self.text_area.appendPlainText(text.rstrip())

    def handle_stderr(self):
        data = self.process.readAllStandardError()
        text = bytes(data).decode("utf-8")
        self.text_area.appendPlainText(text.rstrip())

    def process_finished(self):
        self.text_area.appendPlainText("--- Process finished ---")
        self.process = None
        self.button.setEnabled(True)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

Run this, click the button, and you'll see each line appear one at a time, with the GUI remaining fully responsive throughout.

How QProcess Streams Output in Real Time

When you call self.process.start(), the external command begins running in the background. Qt's event loop keeps spinning, so your window stays responsive.

Each time the external process writes to stdout, QProcess emits readyReadStandardOutput. The connected slot (handle_stdout) reads the available data and appends it to the text area. The same pattern applies for stderr.

When the process exits, the finished signal fires, and we clean up.

Running Complex Bash Commands with QProcess

If your actual command involves sourcing setup files, changing directories, and running build tools — like in the original question — you can pass the entire sequence as a single string to bash -c:

python
command = (
    "source /path/to/setup_file -r && "
    "cd /path/to/parent_directory && "
    "build_project_command"
)
self.process.start("bash", ["-c", command])

This works because bash -c accepts the whole pipeline as one argument.

Streaming Subprocess Output Using QThread and subprocess.Popen

Sometimes QProcess doesn't quite fit your needs. For example, you might need to do additional processing on each line of output before displaying it, or you might need to integrate with Python libraries that expect a file-like object. In these cases, running subprocess.Popen in a background QThread is a good alternative.

The idea: spin up a QThread that runs the subprocess, reads its output line by line, and emits a signal for each line. The main thread receives those signals and updates the GUI safely. If you're new to threading in PyQt6, our guide to multithreading with QThreadPool covers the fundamentals of running background tasks without freezing the GUI.

python
import subprocess
import sys

from PyQt6.QtCore import QThread, pyqtSignal
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPlainTextEdit,
    QPushButton, QVBoxLayout, QWidget,
)


class SubprocessWorker(QThread):
    """Runs a subprocess in a background thread and emits output line by line."""

    output_line = pyqtSignal(str)
    finished_signal = pyqtSignal(int)  # exit code

    def __init__(self, command):
        super().__init__()
        self.command = command

    def run(self):
        process = subprocess.Popen(
            self.command,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,  # Line-buffered
        )

        for line in process.stdout:
            self.output_line.emit(line.rstrip())

        process.wait()
        self.finished_signal.emit(process.returncode)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QThread + Subprocess Live Output")
        self.worker = None

        self.text_area = QPlainTextEdit()
        self.text_area.setReadOnly(True)

        self.button = QPushButton("Run Command")
        self.button.clicked.connect(self.run_command)

        layout = QVBoxLayout()
        layout.addWidget(self.text_area)
        layout.addWidget(self.button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def run_command(self):
        if self.worker is not None:
            return

        self.text_area.clear()
        self.button.setEnabled(False)

        self.worker = SubprocessWorker(
            ["bash", "-c", "for i in 1 2 3 4 5; do echo \"Line $i\"; sleep 1; done"]
        )
        self.worker.output_line.connect(self.on_output_line)
        self.worker.finished_signal.connect(self.on_finished)
        self.worker.start()

    def on_output_line(self, text):
        self.text_area.appendPlainText(text)

    def on_finished(self, exit_code):
        self.text_area.appendPlainText(f"--- Process finished (exit code {exit_code}) ---")
        self.worker = None
        self.button.setEnabled(True)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

How QThread with subprocess.Popen Works

subprocess.Popen (unlike subprocess.run) starts the process and returns immediately, giving you a handle to interact with it. By iterating over process.stdout, you get each line as it's produced.

Because this iteration is blocking (it waits for the next line), we run it in a QThread so it doesn't block the GUI. Each time a line arrives, the worker emits output_line, which is safely delivered to the main thread via Qt's signal-slot mechanism.

Setting bufsize=1 and text=True enables line-buffered mode, which means output is available to read as soon as a newline character is written by the subprocess.

Fixing Delayed Subprocess Output: Buffering Issues

Even with both approaches working correctly on the Qt side, you might still see delayed output if the external program itself buffers its stdout. Many programs buffer output differently when they detect they're writing to a pipe (which is what happens with both QProcess and subprocess.Popen) versus writing to a terminal.

If your external program supports it, you can try:

For example, with the QProcess approach:

python
self.process.start(
    "bash",
    ["-c", "stdbuf -oL your_long_running_command"],
)

QProcess vs QThread: Which Approach Should You Use?

Use QProcess when you're running a simple external command and want a clean, Qt-integrated solution. It handles the event loop integration for you, supports signals for stdout, stderr, and process completion, and doesn't require managing threads.

Use a background QThread when you need more control over how you read the output — for example, if you want to parse each line, filter output, or interact with the subprocess's stdin in complex ways. The thread approach also makes it straightforward to use Python's subprocess module features that don't have direct equivalents in QProcess.

Both approaches keep the GUI responsive and deliver output in real time. Pick whichever fits your situation best.

Complete Example: Live Build Output Viewer in PyQt6

Here's a more polished example that combines the QProcess approach with a few usability improvements — a scrolling output view, a status indicator, and support for running a configurable command. This example uses layouts and basic widgets to build the interface:

python
import sys

from PyQt6.QtCore import QProcess
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import (
    QApplication, QHBoxLayout, QLabel, QLineEdit,
    QMainWindow, QPlainTextEdit, QPushButton,
    QVBoxLayout, QWidget,
)


class BuildOutputViewer(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Live Build Output Viewer")
        self.resize(700, 500)
        self.process = None

        # Command input
        self.command_input = QLineEdit()
        self.command_input.setPlaceholderText(
            "Enter bash command, e.g.: for i in $(seq 1 10); do echo Building step $i; sleep 0.5; done"
        )
        self.command_input.setText(
            "for i in $(seq 1 10); do echo \"Building step $i...\"; sleep 0.5; done && echo Done!"
        )

        # Output area
        self.output_area = QPlainTextEdit()
        self.output_area.setReadOnly(True)
        self.output_area.setFont(QFont("Courier", 10))
        self.output_area.setStyleSheet(
            "QPlainTextEdit { background-color: #1e1e1e; color: #d4d4d4; }"
        )

        # Buttons and status
        self.run_button = QPushButton("Run")
        self.run_button.clicked.connect(self.start_process)

        self.stop_button = QPushButton("Stop")
        self.stop_button.clicked.connect(self.stop_process)
        self.stop_button.setEnabled(False)

        self.status_label = QLabel("Ready")

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.run_button)
        button_layout.addWidget(self.stop_button)
        button_layout.addWidget(self.status_label)
        button_layout.addStretch()

        layout = QVBoxLayout()
        layout.addWidget(self.command_input)
        layout.addLayout(button_layout)
        layout.addWidget(self.output_area)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def start_process(self):
        command = self.command_input.text().strip()
        if not command:
            return

        self.output_area.clear()
        self.run_button.setEnabled(False)
        self.stop_button.setEnabled(True)
        self.status_label.setText("Running...")

        self.process = QProcess(self)
        self.process.readyReadStandardOutput.connect(self.handle_stdout)
        self.process.readyReadStandardError.connect(self.handle_stderr)
        self.process.finished.connect(self.process_finished)

        self.process.start("bash", ["-c", command])

    def stop_process(self):
        if self.process is not None:
            self.process.kill()

    def handle_stdout(self):
        data = self.process.readAllStandardOutput()
        text = bytes(data).decode("utf-8")
        self.output_area.appendPlainText(text.rstrip())

    def handle_stderr(self):
        data = self.process.readAllStandardError()
        text = bytes(data).decode("utf-8")
        self.output_area.appendPlainText(text.rstrip())

    def process_finished(self, exit_code, exit_status):
        status_text = "Finished" if exit_code == 0 else f"Exited with code {exit_code}"
        self.status_label.setText(status_text)
        self.run_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        self.process = None


app = QApplication(sys.argv)
window = BuildOutputViewer()
window.show()
app.exec()

This gives you a terminal-styled output viewer where you can type in a command, run it, watch the output stream in line by line, and stop it if needed — all without the GUI ever locking up.

For an in-depth guide to building Python GUIs with PyQt6 see my book, Create GUI Applications with Python & Qt6.

July 22, 2026 06:00 AM UTC


Mike C. Fletcher

OMI Physics Extension for glTF

I had Claude code up an OMI Physics package using Numpy. It's up on pypi as omi_physics. This follows the OMI extensions to glTF pretty closely to create an engine with the common features you need. It's not trying to be real-world physics, it's just a game engine style simulation.

The core is GL-free, with a few GPGPU kernels to optimise certain bits over 10,000 objects, but in testing any more than about 300 objects brings the rendering below 60fps in OpenGLContext. To get around that we'd need to move the whole physics process into the GPU with a very different solver, and I don't currently need that.

License is MIT, though most of the code is written by Claude, so arguably it's non-copyright.

July 22, 2026 05:13 AM UTC


Seth Michael Larson

PAL GameCube haul from Kraków, Poland (EuroPython 2026)

While I was in Kraków, Poland during EuroPython 2026 I was able to sneak away from sprints and buy a few PAL region GameCube games for my collection. This was on Sunday, which means that many stores are closed until Monday... except for one!Game Over” was open and the owner was very friendly and chatted with me as I browsed the selection.

I purchased three complete-in-box games: The Legend of Zelda: Wind Waker & Ocarina of Time Master Quest (UK), Pikmin (UK), and Billy Hatcher and the Giant Egg (Swedish/Finnish). Prior to these new purchases the only PAL title I owned was Pikmin 2, as this was necessary to complete the Pikmin 2 International Treasure Hoard.

The fun thing about PAL releases is that they are usually the last release a game receives in terms of time, so these revisions often have the most bug fixes and quality-of-life improvements compared to earlier revisions. They also often support multiple languages and have unique box and disk artwork.

The most exciting game I purchased was Legend of Zelda: Wind Waker with the Ocarina of Time Master Quest bonus disk included. This is my first multi-disk GameCube game that I own, and I'm excited to see if it's possible to extract the N64 ROMs from the ISO like Zelda Collector’s Edition. You can see the box has a “flap” with a second disk tray, allowing two games to be included.

The second game is Pikmin, which the PAL region has relatively few changes. The major change is that you can skip the End of Day cutscenes, which you'll likely be watching 10-15 times on a typical playthrough. This small change makes the PAL revision the definitive edition ;)

Another small change for Pikmin is the manual. This game had to introduce the world to Pikmin which are very, very small. Depending on your region the height of Olimar, the protagonist of Pikmin, is compared either to a “US quarter” or a “50 pence piece”.

Finally, Billy Hatcher and the Giant Egg. This is one of my favorite games ever, I've played it through to 99% completion multiple times (the last emblem is not fun to achieve). It's a different game, if you like action adventure then maybe you'll have fun with it, but in particular it's mostly a special title for me. I purchased this one in particular because I didn't have a single “Player’s Choice” for the PAL region which feature silver splines instead of the typical yellow splines for NTSC-J/NTSC.

I was able to dump the ROMs from each of these disks easily using a FlippyDrive-modded GameCube and the included backup utility. I've been thinking about investing in an OmniDrive-compatible disk reader so that I don't have to use my GameCube for this task to avoid tiring out the laser. Maybe a gift idea for the future?



Thanks for reading ♥ I would love to hear your thoughts! Contact me via Mastodon, Bluesky, or email. Browse the blog archive. Check out my blogroll.



July 22, 2026 12:00 AM UTC

July 21, 2026


PyCoder’s Weekly

Issue #744: CPython ABI, CLAUDE.md, Itertools Cheatsheet, and More (2026-07-21)

#744 – JULY 21, 2026
View in Browser »

The PyCoder’s Weekly Logo


What Every Dev Should Know About the CPython ABI

An introduction to the concept of the Application Binary Interface (ABI), the various CPython ABIs, and the new abi3t stable ABI in Python 3.15.
NATHAN GOLDBAUM

How to Write a CLAUDE.md File for Claude Code

Learn how to write a CLAUDE.md file for Claude Code, with global, project, and local examples that capture your Python commands and conventions.
REAL PYTHON

Quiz: How to Write a CLAUDE.md File for Claude Code

REAL PYTHON

Pip Install Actian VectorAI!

alt

VectorAI DB gives your Python AI agents persistent vector memory on your own hardware. No cloud dependency or per-query billing. Native LangChain and LlamaIndex support. On-premises, at the edge, or air-gapped. Free Community Edition available. Get Started Free →
ACTIAN VECTORAI DB sponsor

Itertools Cheatsheet

Cheatsheet with visual diagrams that explain how the iterables from itertools work.
RODRIGO GIRÃO SERRÃO

Python 3.15.0 Beta 4 Released

PYTHON.ORG

PEP 838: Adding python-version to pyvenv.cfg (Added)

PYTHON.ORG

PEP 840: Name Resolution in Class Namespaces (Added)

PYTHON.ORG

PyData Global 2026 Call for Proposals

PYDATA.ORG

Articles & Tutorials

Git for Data Scientists

A practical Git walkthrough for data scientists, focused on real workflows like branching for experiments, reverting mistakes, and keeping project history clean with small, focused commits. It also explains merge vs. rebase, why you should not rebase shared branches, and how to set up .gitignore for data-heavy projects.
KHUYEN TRAN • Shared by Khuyen Tran

In Defense of Not Understanding Your Codebase

In this opinion piece, Sean argues that there is a difference in the thought process between maintaining smaller software projects vs larger ones, and that the former is over represented in engineering discussion in the internet.
SEAN GOEDECKE

Learn Agentic Coding With Claude Code

alt

Unlike a chat window, Claude Code works directly in your project, where it can run your tests and manage your git history. In this two-day live course (August 1–2), you’ll use it to scaffold, test, debug, and ship a Python app project, and leave with a starter kit of reusable skills. See the Full Curriculum →
REAL PYTHON sponsor

Polars: Benchmarking Single Node vs Distributed

Polars has recently added a mechanism for doing distributed calculations. This post describes how that relates to speed-up. As with benchmarking all things, whether it is faster or not depends on your situation.
CHIEL PETERS

Browser Push Notifications for a Django Website

Web Push notifications are an alternate way of getting information to your users. This post shows you how to implement them with Django using a service worker and a Huey background task.
AIDAS BENDORAITIS

12K+ JPEGs From NASA’s Artemis II Mission

Mark writes articles on data analysis. This one is all about the images NASA released from the Artemis II mission. It includes step-by-step instructions that you can follow along.
MARK LITWINTSCHIK

Introducing django-orjson

orjson is a Rust-based replacement for Python’s json module. So what would Adam Johnson do with it? Make it easier to use in Django of course.
ADAM JOHNSON

Stop Using if-else Chains

Learn a cleaner, more extensible way to dispatch logic in Python using dictionaries and function pointers instead of long if-else chains.
KANWAL MEHREEN

Understanding Mixin Classes in Python

Learn how to write reusable Python mixin classes, distinguish them from abstract base classes, and steer clear of common pitfalls.
REAL PYTHON course

Quiz: Understanding Mixin Classes in Python

REAL PYTHON

Creating Presentations in Your Terminal

Spiel is a Python tool for creating terminal based presentations. It uses the Rich package to give you a clean look and feel.
MIKE DRISCOLL

Projects & Code

kademlia-dynamic: Kademlia Distributed Hashtable

GITHUB.COM/F4RSANTOS • Shared by Fernando Santos

userharbor: Framework Agnostic User Management

GITHUB.COM/USERHARBOR

balance: Deal With Biased Data Samples

GITHUB.COM/FACEBOOKRESEARCH

fstache: Fast, Typed, Mustache Renderer

GITHUB.COM/SERVLETCLOUD • Shared by Vladimir Korobkov

bounty-check: Is a GitHub Bounty Issue Still Claimable?

GITHUB.COM/WREN-CASTELLAN • Shared by Wren Castellan

Events

Weekly Real Python Office Hours Q&A (Virtual)

July 22, 2026
REALPYTHON.COM

PyData PyCon Armenia 2026

July 24 to July 26, 2026
PYCON.AM

PyDelhi User Group Meetup

July 25, 2026
MEETUP.COM

Python Sheffield

July 28, 2026
GOOGLE.COM

Python Southwest Florida (PySWFL)

July 29, 2026
MEETUP.COM


Happy Pythoning!
This was PyCoder’s Weekly Issue #744.
View in Browser »

alt

[ 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 ]

July 21, 2026 07:30 PM UTC


PyCharm

What’s New in PyCharm 2026.2

In PyCharm 2026.2, you can build Python extensions with the new Rust plugin and debug them using debugpy, which is now the default engine. Running external utilities is now managed through a redesigned settings UI for uvx, while multi-project setups are supported out of the box for uv, Poetry, and Hatch workspaces. This release also introduces an editor minimap, integrates the Pyrefly engine for faster type insights, adds AI project generation, and more.

Python extension development with the Rust plugin [Beta][Pro]

Work seamlessly with Python projects that leverage Rust modules to speed up performance-critical components.

debugpy as the default debugger

Following its introduction as an optional backend in 2026.1, debugpy is now enabled by default for all Python projects and Jupyter notebooks, using the Debug Adapter Protocol (DAP).

Support for uv-backed tools and uvx

PyCharm now leverages the uv toolchain to streamline how you run your external development utilities, eliminating manual package setups that clutter your local environment.

Support for uv, Poetry, and Hatch multi-projects and uv workspaces [Beta]

Previously available as an optional feature in PyCharm 2026.1.1, this functionality is enabled by default in version 2026.2. It streamlines your subproject management and provides richer dependency insights directly within your configuration files. 

Editor minimap

Navigate complex source files and notebooks more efficiently with the official editor minimap. It provides a high-level visual overview of your document structure across all supported file types – while offering a dedicated layout built just for Jupyter notebooks.

Pyrefly type engine integration

Use Pyrefly as an external type engine to significantly accelerate code insight features for large-scale Python codebases.

Start new projects with AI

If you have a JetBrains AI license, you can now generate fully configured, runnable projects from scratch using natural language prompts directly from the Welcome screen.

Agent skills manager

AI agents are only as useful as the context they have. When they don’t have knowledge of your frameworks, conventions, and tooling, you end up re-explaining the same setup in every new chat window.

Agent skills fix that. Install them once in PyCharm, and your agents carry that domain knowledge across every project and session – automatically. Browse and manage skills directly from the IDE, expand the built-in library with external registries like public GitHub repositories, or let PyCharm import skills you’ve already set up for Claude Code or Codex. 

July 21, 2026 03:53 PM UTC


Rodrigo Girão Serrão

Python quiz: EuroPython 2026 edition

Replay the EuroPython 2026 Python quiz.

These are the questions asked during the EuroPython 2026 quiz. They will test your knowledge of the Python language, the community, and of EuroPython 2026. Since we were celebrating 25 years of EuroPython at EuroPython 2026, some questions also touched on that theme. (Unless explicitly stated, questions refer to CPython 3.14.)

Note that the version of the quiz presented here is less interactive than the one presented at the conference.

Questions

In 25 years of conference, which of these European cities never hosted EuroPython?

A photograph of the city that never hosted EuroPython.

  • Bilbao
  • Birmingham
  • Lisbon
  • Prague

This year's conference programme has it all. This quiz. Talks. Lightning talks. Tutorials. Summits. Open spaces. Talks. And posters during lunch breaks. How many posters are scheduled to be presented at EP 2026?

  • 4
  • 6
  • 12
  • 15

Which of the following Python-related projects has the FEWEST stars on GitHub?

  • CPython
  • Django
  • FastAPI
  • uv

The Python repo has over 130,000 commits made by more than 3,500 contributors over the past 35+ years. The Python core developers are the people with permissions to commit directly to the CPython GitHub repo and plenty of them were at the conference. Out of the following 4 core devs, who were all at the conference, who's made the fewest commits?

  • Guido van Rossum, the creator of Python
  • Hugo van Kemenade, Python 3.14 and 3.15 release manager
  • Łukasz Langa, Python Developer in Residence for ~5 years
  • Pablo Galindo Salgado, Python 3.10 and 3.11 release manager

Speaking of commits, how many commits did Guido van Rossum make?

A screenshot of Guido's contribution graph without any numbers.

Since we're celebrating 25 years of EuroPython, which of the following expressions does not evaluate to 25?

  • 0x19
  • 0b11001
  • 0o33
  • 25

3.15 comes with two new built-in functions. Before that, the previous Python version that got new built-ins was 3.10, with also TWO new built-ins. What two built-ins were introduced in 3.10?

A screenshot of all Python built-in functions in 3.15

  • aiter and anext
  • breakpoint and compile
  • frozendict and sentinel
  • frozenset and memoryview

What's printed by the second print if you run this code?

A screenshot of a snippet of code caching a generator

  • 0
  • 285
  • KeyError
  • ValueError

By the way, speaking of commits, do you still remember how many commits Guido van Rossum made?

A screenshot of Guido's contribution graph without any numbers.

What does the following cursed Python 2 code print?

A tiny cursed snippet of Python code.

  • 'a'
  • 25
  • True
  • SyntaxError

Explanations

Question 1 — Hosting EuroPython

EuroPython 2009 and 2010 was hosted in Birmingham. EuroPython 2015 and 2016 was hosted in Bilbao. EuroPython 2023, 2024, and 2025 was hosted in Prague. Of the four options, Lisbon is the only European city that never hosted an EuroPython.

Question 2 — poster presentations

Originally, 9 poster presentations were scheduled. After a mixup and a couple cancellations we ended with only 6.

Question 3 — GitHub stars

The official quiz asked you to order all four projects, from most stars to least stars. Can you do it?

On the 15th of July of 2026, this would be the correct ordering:

  1. FastAPI, 101k
  2. Django, 88.2k
  3. uv, 87.5k
  4. CPython, 73.8k

Question 4 — commits

On the 15th of July of 2026, GitHub reported the following number of all-time...

July 21, 2026 03:00 PM UTC


Python Bytes

#489 Or JSON?

<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://adamj.eu/tech/2026/07/15/introducing-django-orjson/?featured_on=pythonbytes">django-orjson</a></strong></li> <li><strong><a href="https://www.peterbe.com/plog/best-django-redis-configuration-for-speed-and-size?featured_on=pythonbytes">Best Django Redis configuration for speed and size</a></strong></li> <li><strong>Linus Torvalds <a href="https://lore.kernel.org/linux-media/CAHk-=wi4zC+Ze8e+p3tMv8TtG_80KzsZ1syL9anBtmEh5Z40vg@mail.gmail.com/?featured_on=pythonbytes">puts the foot down</a> against Anti-AI Kernel Maintainers</strong></li> <li><strong><a href="https://www.djangoproject.com/weblog/2026/jul/15/supporting-the-triptych-project/?featured_on=pythonbytes">Django Steering Council backs the Triptych Project</a></strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=zaoPcuKz970' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="489">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href="https://training.talkpython.fm/?featured_on=pythonbytes"><strong>courses at Talk Python</strong></a></li> <li>Consulting from <a href="https://sixfeetup.com/?featured_on=pythonbytes"><strong>Six Feet Up</strong></a></li> </ul> <p><strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href="https://fosstodon.org/@mkennedy">Mastodon</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/mkennedy?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/mkennedy/?featured_on=pythonbytes">LinkedIn</a></li> <li>Calvin: <a href="https://sixfeetup.social/@calvin?featured_on=pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/calvinhp.com?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/calvinhp?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/calvinhp/?featured_on=pythonbytes">LinkedIn</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/pythonbytes.fm">BlueSky</a> / <a href="https://x.com/PythonBytes?featured_on=pythonbytes">X</a></li> </ul> <p>Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually <strong>Tuesday at 7am PT</strong>. Older video versions available there too.</p> <p><strong>Michael #1: <a href="https://adamj.eu/tech/2026/07/15/introducing-django-orjson/?featured_on=pythonbytes">django-orjson</a></strong></p> <ul> <li><strong>Adam Johnson dropped <code>django-orjson</code></strong> - drop-in replacements for the Django and DRF pieces that touch JSON, swapping stdlib <code>json</code> for <strong>orjson</strong>, the Rust-based library. Headline numbers: <strong>10x faster serialization, 2x faster deserialization</strong>.</li> <li><strong>The interesting question is why this needs to be a package at all.</strong> <code>pip install orjson</code> is the easy part. Adam's actual pitch: adopting it "isn't easy, especially when your framework uses <code>json</code> in many different parts." Django scatters JSON across <code>JsonResponse</code>, the test client and test case classes, the <code>json_script</code> template tag, and more. There's no single hook to grab, so you get a library that catches them all.</li> <li><strong>Adam is refreshingly honest about the scale of the win.</strong> His words: <em>"While database queries tend to dominate the typical Django application's runtime, the time spent in serialization and deserialization can still be significant."</em> He calls it <strong>"a nearly free performance win"</strong> - not "this will 10x your app." That's a claim about <em>cost</em>, not magnitude, and it's worth keeping those straight.</li> <li><strong>Worth flagging what the post doesn't cover: caveats.</strong> There are none in the article, but orjson has real ones. Django and Flask both render datetimes as RFC 822 HTTP-date (<code>Wed, 15 Jul 2026 12:00:00 GMT</code>); orjson does ISO 8601. It can't do <code>ensure_ascii</code>, it rejects NaN and Infinity (which stdlib happily emits), and it raises on <code>Decimal</code>. If you've got a JS client parsing dates, that's a wire-format change.</li> <li><strong>Who should actually take this?</strong> If you're a DRF shop shoveling JSON all day, yes - it's cheap and it's real. If your app mostly renders HTML templates, you're optimizing a slice of runtime that's already near zero.</li> <li><strong>The problem Adam's package solves doesn't exist in Flask or Quart.</strong> They already centralize every JSON operation - <code>jsonify</code>, <code>request.get_json()</code>, the test client, the <code>|tojson</code> filter - behind one provider object at <code>app.json</code>. So there's no library to install. It's about ten lines: <div class="codehilite"> <pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">orjson</span> <span class="kn">from</span><span class="w"> </span><span class="nn">quart.json.provider</span><span class="w"> </span><span class="kn">import</span> <span class="n">JSONProvider</span> <span class="c1"># or flask.json.provider</span> <span class="k">class</span><span class="w"> </span><span class="nc">OrjsonProvider</span><span class="p">(</span><span class="n">JSONProvider</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="nf">dumps</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span> <span class="k">return</span> <span class="n">orjson</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span> <span class="c1"># provider must return str</span> <span class="k">def</span><span class="w"> </span><span class="nf">loads</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> <span class="k">return</span> <span class="n">orjson</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="n">app</span><span class="o">.</span><span class="n">json</span> <span class="o">=</span> <span class="n">OrjsonProvider</span><span class="p">(</span><span class="n">app</span><span class="p">)</span> </code></pre> </div></li> </ul> <p><strong>The numbers on <a href="https://talkpython.fm/?featured_on=pythonbytes">talkpython.fm</a></strong></p> <ul> <li><strong>Evaluated it, measured it, and skipped it.</strong> The biggest JSON payload we serve is our MCP server returning a cached episode transcript, about 139 KB. Swapping the provider saves <strong>0.119 milliseconds per request</strong>. That total response takes 1.1 ms</li> <li><strong>We got 4.1x, not 10x - and the reason is the good lesson.</strong> Payload <em>shape</em> decides your speedup. The 10x is for structure-heavy data, lots of small keys where stdlib burns time in Python-level dispatch per item. Our hot payload is one giant transcript string, so the work is escaping and memcpy</li> </ul> <p><strong>Calvin #2: <a href="https://www.peterbe.com/plog/best-django-redis-configuration-for-speed-and-size?featured_on=pythonbytes">Best Django Redis configuration for speed and size</a></strong></p> <ul> <li>Peter Bengtsson revisits a classic: his 2017 "<a href="https://www.peterbe.com/plog/fastest-redis-optimization-for-django?featured_on=pythonbytes">Fastest Redis configuration for Django</a>" benchmark now has a 2026 update posted this week.</li> <li>The 2017 post pitted django-redis serializers (json, ujson, msgpack, pickle) and compressors (zlib, lzma) against each other; conclusion was <strong>msgpack + zlib</strong> as the sweet spot - avoid the json serializer, it's fat and slow.</li> <li>The 2026 update narrows focus to just compressors: default (no compression), <code>zlib</code>, <code>lzma</code>, and newcomer <code>zstd</code>.</li> <li>New results: <code>lzma</code> compresses best but is slowest; <code>zstd</code> is the fastest compressor on Ubuntu; differences between them are very small.</li> <li>Big takeaway across both: compression buys you a lot of space (2–3.5x smaller) for very little speed cost - worth it for Redis where memory is the constraint.</li> <li>Caveat from the author: results depend heavily on your data - his test stores short strings of numbers, so benchmark your own workload.</li> </ul> <p><strong>Michael #3: Linus Torvalds <a href="https://lore.kernel.org/linux-media/CAHk-=wi4zC+Ze8e+p3tMv8TtG_80KzsZ1syL9anBtmEh5Z40vg@mail.gmail.com/?featured_on=pythonbytes">puts the foot down</a> against Anti-AI Kernel Maintainers</strong></p> <ul> <li>Write up <a href="https://arstechnica.com/ai/2026/07/linus-torvalds-to-critics-of-ai-coding-in-linux-fork-it-or-just-walk-away/?featured_on=pythonbytes">on Ars</a>.</li> <li>Really good coverage by Maximillian: <a href="https://www.youtube.com/watch?v=kxEoF8sn-K4">Time to wake up (for some)</a></li> <li>Torvalds said that “Linux is not one of those anti-AI projects, and if somebody has issues with that, they can do the open-source thing and fork it. Or just walk away.”</li> <li>I agree with Max, putting your head in the sand and waiting for AI to go away will likely mean you won’t be working professionally in software development in the coming years.</li> <li>The statement came amid a lengthy thread arguing about the use of <a href="https://github.com/sashiko-dev/sashiko?featured_on=pythonbytes">Sashiko</a>, an “agentic Linux kernel code review system” that its creators claim can, in tests, independently find 53.6 percent of the bugs that would end up being fixed by human coders in later commits.</li> <li>“We’re not forcing anybody to use [LLM tools], but I will very loudly ignore people who try to argue against other people from using it,” Torvalds said.</li> <li>“Anybody who points to the problems at AI had better be looking in the mirror and pointing at themselves at the same time,” Torvalds wrote.</li> </ul> <p><strong>Calvin #4: <a href="https://www.djangoproject.com/weblog/2026/jul/15/supporting-the-triptych-project/?featured_on=pythonbytes">Django Steering Council backs the Triptych Project</a></strong></p> <ul> <li>Django Steering Council issued a Letter of Collaboration backing Carson Gross &amp; Alex Petros's funding bid for the <a href="https://triptychproject.org/?featured_on=pythonbytes">Triptych Project</a> - three proposals to make HTML more expressive natively, in every browser.</li> <li>The three additions: PUT/PATCH/DELETE methods for forms, button actions (buttons that fire HTTP requests without a wrapping form), and partial page replacement.</li> <li>Distills the core ideas from HTMX/Unpoly/Turbo into the HTML standard itself - no JS, no library, nothing to ship or maintain.</li> <li>Current focus is button actions (<a href="https://github.com/whatwg/html/issues/12330?featured_on=pythonbytes">WHATWG #12330</a>): <code>&lt;button action=/logout method=POST&gt;Logout&lt;/button&gt;</code> instead of wrapping a button in a form.</li> <li>Relevant to Django directly - think the admin submit row and disguised delete links; Django 6.0's template partials were already inspired by these patterns.</li> <li>How to help: companies can send non-binding letters of support on letterhead; individuals can read the proposals and weigh in on the WHATWG issues.</li> </ul> <p><strong>Extras</strong></p> <p>Calvin:</p> <ul> <li><a href="https://github.com/petergpt/doomql?featured_on=pythonbytes">DOOMQL</a> - <strong>A playable first-person shooter whose framebuffer is a SQL query.</strong></li> </ul> <p>Michael:</p> <ul> <li><a href="https://github.com/emmett-framework/granian/releases/tag/v2.7.9?featured_on=pythonbytes"><strong>Granian 2.7.9 fixes WSGI threadpool scheduler starvation/underscaling</strong></a></li> <li><a href="https://talkpython.fm/blog/posts/calvin-hendryx-parker-joins-python-bytes-as-co-host/?featured_on=pythonbytes">Welcome Calvin post</a></li> </ul> <p><strong>Joke: <a href="https://x.com/PR0GRAMMERHUM0R/status/2077211586440151184?featured_on=pythonbytes">Solving all bugs</a></strong></p>

July 21, 2026 08:00 AM UTC


Talk Python Blog

Calvin Hendryx-Parker joins Python Bytes as Co-Host

Calvin and Michael kicking off a Python Bytes episode

TL;DR: Calvin Hendryx-Parker is the new permanent co-host of Python Bytes, starting with episode 483 on June 9th, 2026. After almost 10 years, Brian Okken, who founded the show with me back in 2016, has decided it’s time for him to move on.


We have some major news to announce over at Python Bytes. We are welcoming a new co-host to the show: Calvin Hendryx-Parker. After almost 10 years, Brian Okken who founded the show with me, Michael back in 2016 has decided it’s time for him to move on. On the air I called it the next generation of Python Bytes.

July 21, 2026 02:46 AM UTC