skip to navigation
skip to content

Planet Python

Last update: October 04, 2026 04:48 AM UTC

October 03, 2026


Brian Okken

PyBay 2026 Talk info and slides

Talk Title: Is TDD even relevant anymore? Yes, but don’t be dumb about it.

Event: PyBay 2026

Slides: tdd-pybay-2026.pdf

October 03, 2026 12:00 AM UTC

October 02, 2026


Python Software Foundation

Announcing the 2026 PSF Board Election Results!

October 02, 2026 09:27 AM UTC


Talk Python to Me

#565: Tachyon, Python 3.15's Built-in Sampling Profiler

Do you know what's actually slow in your Python app? Or are you guessing? Until now, profiling Python meant a tracing profiler that made your code 2 to 3 times slower. Or a third-party tool that broke with every new release. Python 3.15 fixes that. It ships Tachyon, a sampling profiler built into the standard library. It attaches to live production apps with almost zero overhead. My guests are Pablo Galindo Salgado, CPython core developer and Steering Council member, and László Kiss Kollár from Bloomberg's Python infrastructure team. Their first prototype ran at two samples a second. Now it does over a million hz. And it lands in Python 3.15 this October.

October 02, 2026 12:09 AM UTC


Python Insider

Python 3.15.0 candidate 3 is here!

Following tradition, a surprise rc3!

October 02, 2026 12:00 AM UTC

October 01, 2026


EuroPython

Humans of EuroPython: Diego Russo

“You stop seeing EuroPython only as an event you attend and start seeing the people, effort and collaboration that make it possible. It gives you a much stronger sense of belonging to the community.” An Interview with Diego Russo.

October 01, 2026 11:56 PM UTC


Django Weblog

Nominate Someone for the 2026 Malcolm Tredinnick Memorial Prize

Hello Everyone 👋

It is that time of year again when we recognize someone from our community in memory of our friend Malcolm.

Malcolm was an early core contributor to Django and had a huge influence on the Django we know today. Besides being knowledgeable, he was also especially friendly to new users and contributors. He exemplified what it means to be an amazing Open Source contributor. We still miss him to this day.

The prize

Our prizes page summarizes it nicely:

The Malcolm Tredinnick Memorial Prize is a monetary prize, awarded annually, to the person who best exemplifies the spirit of Malcolm’s work - someone who welcomes, supports, and nurtures newcomers; freely gives feedback and assistance to others, and helps to grow the community. The hope is that the recipient of the award will use the award stipend as a contribution to travel to a community event -- a DjangoCon, a PyCon, a sprint -- and continue in Malcolm’s footsteps.

Please make your nominations using our form: 2026 Malcolm Tredinnick Memorial Prize nominations. Django Software Foundation board members, Django Fellows, and the Treasurer’s assistant are not eligible to receive the prize. You are still welcome to use the appreciation field below to recognize them, and we’ll make sure those messages are passed along.

We will take nominations until Saturday, October 15th, 2026, 23:59 Anywhere on Earth, and will announce the results in late October. If you have any questions, please use our dedicated forum thread or contact the DSF Board.

October 01, 2026 01:51 PM UTC


Python Insider

Python 3.10.22, 3.11.17, 3.12.15, 3.13.16 and 3.14.8 are now available!

Security updates across Python 3.10–3.14, the final maintenance release of 3.13, and a farewell to Python 3.10.

October 01, 2026 12:00 AM UTC


Graham Dumpleton

Getting to know Tachyon

Python 3.15 ships with a new profiler. It is called Tachyon, it lives in the standard library as the profiling.sampling module, and unlike cProfile it is a sampling profiler rather than a tracing one. I now have a set of hands-on workshops for it, which you can find at github.com/GrahamDumpleton/tachyon-workshops or on the workshops page of this site, and they have reached the point where I am happy for other people to do them. That said, they were not written for other people in the first place. They were written so I could learn Tachyon myself, and the reason I wanted to learn it had little to do with profiling as such.

Why I wrote them

For the past while I have been working on wrapture, a library built on top of wrapt for attaching bindings to arbitrary call sites in a Python program without modifying the code being observed, then doing something useful with what flows through those call sites, such as recording calls or exporting traces. Everything wrapture does happens from inside the process. It wraps functions, and when they are called it is there in the call path to see the arguments, the return value, the exception, and the time taken.

So when a profiler landed in the standard library the obvious question was whether wrapture could make use of it. Could Tachyon be offered as part of wrapture's tracing, so that a binding on a call site could also tell you what the program was doing underneath that call? Could the two share anything at all? I did not know, and reading the Tachyon documentation was not going to tell me, because the documentation describes what each option does and not what you would want it for. The only way I was going to get a real answer was to use every part of the profiler on real programs, and the way I have been doing that lately is to have workshops written for the thing I want to learn and then do the workshops.

Working from the outside

A sampling profiler does not instrument your program. Tachyon runs as a separate process, reads the call stack of the target process from the outside, a thousand times a second by default, and counts what it finds. The program runs at full speed and never knows it is being watched. The counts are then an estimate of where time went: a function that appears in half the samples took about half the time. cProfile, which in 3.15 has moved to profiling.tracing, does the opposite. It hooks every function call and return, so the counts are exact, but a program made of millions of small calls can run several times slower under it.

One of the early workshops puts the same program under both and the difference is stark enough that it answers the question of which to reach for most of the time. The other consequence of working from the outside is that the profiler needs permission to read another process's memory. Linux lets a user do that to processes they started themselves. macOS and Windows do not without root or administrator rights, which is why the workshops only run on Linux, and why on a Mac the way to run them is in a container. More on that below.

Where that leaves wrapture

My first impression, having now been through all of it, is that the two do not fit together. Tachyon works by looking in at a process from outside. wrapture works by being inside the process, in the call path. I have found nothing to suggest Tachyon can be driven from within the process it is profiling in the way wrapture would need, and nothing in the way it reads a process that a binding on a call site could hook into. The models are different enough that I cannot see a way of marrying them, at least not with what is in 3.15.

That is a perfectly good answer. I would rather know it now than have spent time trying to bolt one onto the other, and the reasoning behind it is grounded in having used every mode of the profiler rather than guessed from a page of options. If that changes in a later release, or if someone knows something about Tachyon's internals that I missed, I would like to hear about it. For now the question is parked, not closed.

Learning by having the lesson written

What surprised me was how much better this worked as a way of learning than reading the documentation would have. I did not write the workshops by hand. I described what I wanted each one to teach, had an AI build it, then did the workshop. The useful part was not that the AI knew Tachyon, since the documentation knew Tachyon just as well. It was that the AI kept filling in the context the documentation leaves out: why wall-clock time and CPU time disagree and what that disagreement tells you about the fix, why a view of which thread holds the GIL exists at all, why you would record a profile in the binary format and look at it later rather than looking at it now. Each feature came with a small program written to show it, which had the problem the feature was there to find, and that is what made the feature stick.

This is a different way to use an AI for learning than asking it questions. Asking questions gets you answers at the level of the question. Asking for the lesson to be built gets you something you then have to work through with your own hands, and if the lesson is wrong you find out when the check at the end of a step does not pass. It is also, I have noticed, far closer to how I actually learned things in the first place, which was by having to teach them.

What the workshops cover

There are two collections, with a catalog in the repository so that one URL offers both.

The first, "Profiling with Tachyon", is fourteen workshops and a little under four hours in total. It starts with running the profiler on a small report generator and reading the table it prints, so that you know what a sample is and why time is samples multiplied by an interval. It then puts a program under both the tracer and the sampler, and looks at how the profiler reads a process from outside and what that needs permission for. From there it moves through the pictures the profiler produces: the interactive flame graph, the heatmap that paints sample counts onto the source so a single expensive line stands out, and recording in the binary format to replay later as a table, a flame graph, a heatmap, a Firefox Profiler file, JSON lines or a pstats file. The next group is about choosing what to measure, with workshops on wall-clock against CPU time, threads and the GIL, the cost of exceptions, and async code profiled as tasks and awaits rather than as the event loop's stack. The last group looks closer, at the markers for native code and the garbage collector, at opcode-level profiling in the heatmap, at differential flame graphs for telling whether a fix worked, and at the live terminal view that watches one process like top.

The second, "Tachyon on real applications", is seven workshops and a little over two hours. Each one runs a realistic program in one terminal and drives it from a second, with the profiler between them. There is a Flask application under load, then a slow endpoint found with the flame graph, pinned to a line with the heatmap, fixed, and proven fixed with a second recording. There is a Starlette service under uvicorn whose searches stall when articles are read, including a fix with a thread that does not work and why, and one with a process pool that does. There are worker processes, both a process pool and gunicorn with three workers, profiled with --subprocesses. There is a pytest suite with the profiler wrapped around it, attaching to a server that was started without the profiler, and finally a profile recorded in production, carried back and compared in a notebook with a table and a chart.

Every workshop ships the program it profiles, and the flame graphs and heatmaps open in JupyterLab in a tab beside the code, so you are reading the picture and the source together rather than switching between a browser and an editor.

Running them

The workshops are built on jupyterlab-workshop, a JupyterLab extension that shows the instructions in a side panel with clickable actions that drive the session, opening files, running commands in a terminal and running cells in a notebook, and checks what you have done as you go.

The easiest way to start is the Binder button in the repository README, which builds the repository into a temporary JupyterLab on mybinder.org with nothing to install and no account needed. The session is thrown away when you are done, so finish a workshop in the session you started it in, and shut the session down from the Finish dialog or the File menu rather than just closing the tab, so the resources go back for other people. The Codespaces button does the same in a container tied to your GitHub account, which persists until you delete it and uses your account's monthly allowance.

If you want to run them on your own machine and that machine is a Mac, the Linux requirement means a container. The repository carries a Dockerfile for it, with Python 3.15, JupyterLab, the extension and a non-root user, and the checkout is mounted so everything the workshops write ends up in your checkout:

git clone https://github.com/GrahamDumpleton/tachyon-workshops
cd tachyon-workshops
docker build -t tachyon-workshops -f container/Dockerfile .
docker run --rm --init -it -p 127.0.0.1:8888:8888 -e JUPYTER_TOKEN=tachyon \
    -v "$PWD":/home/learner/tachyon-workshops tachyon-workshops

Then open http://127.0.0.1:8888/?token=tachyon. On Linux with Python 3.15 and uv you can skip the clone entirely and launch the extension on the catalog directly, with a directory of your own to keep the workshops in:

uvx --python 3.15 --from "jupyterlab-workshop[lab]" jupyter-workshop launch \
    --root ~/training --catalog https://raw.githubusercontent.com/GrahamDumpleton/tachyon-workshops/main/catalog.json

Python 3.15 is named because each workshop builds its own environment from the Python that JupyterLab runs on, and the profiler and the program it profiles must be the same Python. If you already have the extension installed somewhere, you can also just subscribe to the catalog from the workshop browser and the collections are offered there.

What's next

Whether Tachyon ends up having anything to do with wrapture, I know a good deal more about it than I did, and I have something to show for the time that other people can use. If you do the workshops and find a step that is unclear, or one that does not work for you, the issue tracker is the place to say so. Fingers crossed they are as useful a way into Tachyon for you as writing them was for me.

October 01, 2026 12:00 AM UTC

September 30, 2026


"Michiel's Blog"

Monitoring my Samsung smart washing machine without a cloud

No SmartThings for me!

September 30, 2026 08:00 PM UTC


Python Insider

Python Language Summit 2026

The 2026 Python Language Summit was hosted in Kraków, Poland as part of EuroPython 2026. There were 15 talks covering free-threading, Rust, garbage collection, type annotations, and more.

September 30, 2026 12:00 PM UTC

Lightning Talks (Python Language Summit 2026)

Lightning talks on a one-time ABI break, safer interruptions, EktuPy (Scratch but Python), an AGENTS.md file for CPython, and a call to read PEP 836.

September 30, 2026 12:00 PM UTC

PEP 827: Type Manipulation (Python Language Summit 2026)

Michael J. Sullivan presents PEP 827 and discusses a key design decision: how to store type annotations?

September 30, 2026 12:00 PM UTC

Free-Threaded Python Post-Era (Python Language Summit 2026)

Tobias Wrigstad, Fridtjof Stoldt, and Donghee Na propose a safe and performant, high-level concurrency model for free-threaded Python

September 30, 2026 12:00 PM UTC

Developer-in-Residence Update & Future (Python Language Summit 2026)

Petr Viktorin gives an update on the Developer-in-Residence role and asks Python core developers for projects to prioritize

September 30, 2026 12:00 PM UTC

Spicycrab (Python Language Summit 2026)

Kushal Das shows off Spicycrab, a Python-to-Rust transpiler for Python users who need performance without learning Rust or leaving Python

September 30, 2026 12:00 PM UTC

Rust for CPython (Python Language Summit 2026)

David Hewitt shares a status update, first module, and potential acceptance criteria for the Rust for CPython project

September 30, 2026 12:00 PM UTC

Memory Snapshots for CPython (Python Language Summit 2026)

Hood Chatham proposes memory snapshots and an initialization phase for speedier Python startups

September 30, 2026 12:00 PM UTC

Memory Buffer Protocol (Python Language Summit 2026)

Nathan Goldbaum proposes safe concurrent access through buffer leases and custom data types for the Python Buffer Protocol.

September 30, 2026 12:00 PM UTC

Garbage Collection: Generational? Incremental? Both! (Python Language Summit 2026)

Mark Shannon proposes a future garbage collection strategy for Python following the revert of the incremental garbage collector in Python 3.14

September 30, 2026 12:00 PM UTC

macOS and Python (Python Language Summit 2026)

Ned Deily weighs whether Python should continue shipping macOS installers

September 30, 2026 12:00 PM UTC

One namespace to namespace them all (Python Language Summit 2026)

Pablo Galindo Salgado proposes a top-level `std` namespace for the Python standard library to prevent module shadowing and free up module names.

September 30, 2026 12:00 PM UTC


Python Software Foundation

Python Language Summit 2026 blog posts are now available

September 30, 2026 09:22 AM UTC


Python GUIs

Changing Ticks in PyQtGraph Objects to Strings — How to replace numeric axis ticks with custom labels like month names or dates in PyQtGraph

How do I replace the numeric tick labels on a PyQtGraph axis with custom strings, like month names or category labels?

When you're plotting data in PyQtGraph, the axes default to showing numeric tick values. That's fine for a lot of use cases, but sometimes your x-axis represents something more meaningful — like months of the year, days of the week, or category names. In those situations, you want to replace those numbers with readable string labels.

In this tutorial, you'll learn how to customize the tick labels on a PyQtGraph axis so they display strings instead of numbers.

How axis ticks work in PyQtGraph

PyQtGraph plots data using numeric values on both axes. When your x-axis data represents categories — like months — you typically use integers (1, 2, 3, ...) as placeholders and then map those integers to human-readable labels.

To do this, you need to:

  1. Create a list of 2-tuples, where each tuple pairs a numeric tick position with a string label.
  2. Get the axis object from the plot widget.
  3. Call .setTicks() on that axis, passing your labels wrapped in a list.

Let's walk through a complete example.

Setting up the plot

We'll create a simple line plot showing temperature values across 10 months. The x-axis will use integers from 1 to 10 to represent months, and we'll replace those numbers with actual month names.

First, we generate the tick labels using Python's datetime module:

python
import datetime

months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

month_labels = [
    (m, datetime.date(2020, m, 1).strftime("%B"))
    for m in months
]

This gives us a list like:

python
[(1, "January"), (2, "February"), (3, "March"), ...]

Each tuple maps a position on the x-axis (the integer) to a label (the month name). The strftime("%B") call converts a date into its full month name.

Applying the tick labels to the axis

Once you have your list of label tuples, you apply them to the bottom axis of the plot widget:

python
ax = self.graphWidget.getAxis("bottom")
ax.setTicks([month_labels])

Notice that month_labels is passed inside another list — so you're calling ax.setTicks([month_labels]), not ax.setTicks(month_labels). This is because setTicks() accepts a list of tick levels (for major ticks, minor ticks, etc.). By passing a single list inside the outer list, you're setting the major tick labels.

Complete working example

Here's the full application. You can copy and run this directly. If you're new to PyQtGraph, you may want to first read our introduction to plotting with PyQtGraph to understand the basics.

python
from PyQt6 import QtWidgets
from pyqtgraph import PlotWidget
import pyqtgraph as pg
import sys
import datetime


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        temperature = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45]

        self.graphWidget.setBackground("w")

        pen = pg.mkPen(color=(255, 0, 0))

        # Create a list of (tick_value, tick_label) tuples.
        month_labels = [
            (m, datetime.date(2020, m, 1).strftime("%B"))
            for m in months
        ]

        self.graphWidget.plot(months, temperature, pen=pen)

        # Get the bottom axis and apply our custom tick labels.
        ax = self.graphWidget.getAxis("bottom")
        ax.setTicks([month_labels])


app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())

Running this produces a plot where the x-axis displays month names instead of numbers:

Plot with month name tick labels on the x-axis

Using any string labels

This approach works with any strings, not just month names. If you wanted to label the x-axis with arbitrary category names, you'd follow the same pattern:

python
labels = [
    (0, "Apples"),
    (1, "Oranges"),
    (2, "Bananas"),
    (3, "Grapes"),
]

ax = self.graphWidget.getAxis("bottom")
ax.setTicks([labels])

As long as you match each label string to the correct numeric position used in your data, the ticks will line up with your plot points.

If you want to plot time series data specifically, take a look at plotting time series data with PyQtGraph for a more detailed approach. You can also embed PyQtGraph into a larger custom widget for more complex applications, or combine it with Matplotlib plotting if you need additional chart types.

September 30, 2026 06:00 AM UTC


Seth Michael Larson

Blogging for the Python Language Summit

September 30, 2026 12:00 AM UTC

September 29, 2026


PyCoder’s Weekly

Issue #754: Jev, PyO3, Temp Files, and More (2026-09-29)

September 29, 2026 07:30 PM UTC