Planet Python
Last update: January 13, 2026 07:44 PM UTC
January 13, 2026
PyCoder’s Weekly
Issue #717: Unit Testing Performance, Cursor, Recursive match, and More (Jan. 13, 2026)
#717 – JANUARY 13, 2026
View in Browser »
Unit Testing Your Code’s Performance
Testing your code is important, but not just for correctness also for performance. One approach is to check performance degradation as data sizes go up, also known as Big-O scaling.
ITAMA TURNER-TRAURING
Tips for Using the AI Coding Editor Cursor
Learn Cursor fast: AI-powered coding with agents, project-aware chat, inline edits, and VS Code workflow – ship smarter, sooner.
REAL PYTHON course
AI Code Review With Comments You’ll Actually Implement
Unblocked is the AI code review that surfaces real issues and meaningful feedback instead of flooding your PRs with stylistic nitpicks and low-value comments. “Unblocked made me reconsider my AI fatigue. ” - Senior developer, Clio. Try now for Free →
UNBLOCKED sponsor
Recursive Structural Pattern Matching
Learn how to use structural pattern matching (the match statement) to work recursively through tree-like structures.
RODRIGO GIRÃO SERRÃO
Articles & Tutorials
Coding Python With Confidence: Live Course Participants
Are you looking for that solid foundation to begin your Python journey? Would the accountability of scheduled group classes help you get through the basics and start building something? This week, two members of the Python for Beginners live course discuss their experiences.
REAL PYTHON podcast
Regex: Searching for the Tiger
Python’s re module is a robust toolset for writing regular expressions, but its behavior often deviates from other engines. Understanding the nuances of the interpreter and the Unicode standard is essential for writing predictable patterns.
SUBSTACK.COM • Shared by Vivis Dev
The Ultimate Guide to Docker Build Cache
Docker builds feel slow because cache invalidation is working against you. Depot explains how BuildKit’s layer caching works, when to use bind mounts vs cache mounts, and how to optimize your Dockerfile so Gradle dependencies don’t rebuild on every code change →
DEPOT sponsor
How We Made Python’s Packaging Library 3x Faster
Underneath pip, and many other packaging tools, is the packaging library which deals with version numbers and other associated markers. Recent work on the library has shown significant speed-up and this post talks about how it was done.
HENRY SCHREINER
Django Quiz 2025
Last month, Adam held another quiz at the December edition of Django London. This is an annual tradition at the meetup, now you can take it yourself or just skim the answers.
ADAM JOHNSON
Live Python Courses: Already 50% Sold for 2026
Real Python’s instructor-led cohorts are filling up. Python for Beginners builds your foundation right the first time. Intermediate Python Deep Dive covers decorators, OOP, and production patterns with real-time expert feedback. Grab a seat before they’re gone at realpython.com/live →
REAL PYTHON sponsor
A Different Way to Think About Python API Clients
Paul is frustrated with how clients interact with APIs in Python, so he’s proposing a new approach inspired by the many decorator-based API server libraries.
PAULWRITES.SOFTWARE • Shared by Paul Hallett
Learn From 2025’s Most Popular Python Tutorials and Courses
Pick from the best Python tutorials and courses of 2025. Revisit core skills, 3.14 updates, AI coding tools, and project walkthroughs. Kickstart your 2026!
REAL PYTHON
Debugging With F-Strings
If you’re debugging Python code with print calls, consider using f-strings with self-documenting expressions to make your debugging a little bit easier.
TREY HUNNER
How to Switch to ty From Mypy
The folks at Astral have created a type checker known as “ty”. This post describes how to move from Mypy to ty, including in your GitHub Actions.
MIKE DRISCOLL
Recent Optimizations in Python’s Reference Counting
This article highlights some of the many optimizations to reference counting that have occurred in recent CPython releases.
ARTEM GOLUBIN
Projects & Code
Events
Weekly Real Python Office Hours Q&A (Virtual)
January 14, 2026
REALPYTHON.COM
PyData Bristol Meetup
January 15, 2026
MEETUP.COM
PyLadies Dublin
January 15, 2026
PYLADIES.COM
Chattanooga Python User Group
January 16 to January 17, 2026
MEETUP.COM
DjangoCologne
January 20, 2026
MEETUP.COM
Inland Empire Python Users Group Monthly Meeting
January 21, 2026
MEETUP.COM
Happy Pythoning!
This was PyCoder’s Weekly Issue #717.
View in Browser »
[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]
Real Python
Intro to Object-Oriented Programming (OOP) in Python
Object-oriented programming (OOP) is one of the most significant and essential topics in programming. This course will give you a foundational conceptual understanding of object-oriented programming to help you elevate your Python skills.
You’ll learn how to define custom types using classes and how to instantiate those classes into Python objects that can be used throughout your program.
Finally, you’ll discover how classes can inherit from one another, with a brief introduction to inheritance, enabling you to write maintainable and less redundant Python code.
[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]
Python Software Foundation
Anthropic invests $1.5 million in the Python Software Foundation and open source security
We are thrilled to announce that Anthropic has entered into a two-year partnership with the Python Software Foundation (PSF) to contribute a landmark total of $1.5 million to support the foundation’s work, with an emphasis on Python ecosystem security. This investment will enable the PSF to make crucial security advances to CPython and the Python Package Index (PyPI) benefiting all users, and it will also sustain the foundation’s core work supporting the Python language, ecosystem, and global community.
About Anthropic
Talk Python to Me
#534: diskcache: Your secret Python perf weapon
Your cloud SSD is sitting there, bored, and it would like a job. Today we’re putting it to work with DiskCache, a simple, practical cache built on SQLite that can speed things up without spinning up Redis or extra services. Once you start to see what it can do, a universe of possibilities opens up. We're joined by Vincent Warmerdam to dive into DiskCache.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br> <a href='https://talkpython.fm/devopsbook'>Python in Production</a><br/> <br/> <h2 class="links-heading mb-4">Links from the show</h2> <div><strong>diskcache docs</strong>: <a href="https://grantjenks.com/docs/diskcache/?featured_on=talkpython" target="_blank" >grantjenks.com</a><br/> <strong>LLM Building Blocks for Python course</strong>: <a href="https://training.talkpython.fm/courses/llm-building-blocks-for-python" target="_blank" >training.talkpython.fm</a><br/> <strong>JSONDisk</strong>: <a href="https://grantjenks.com/docs/diskcache/api.html#jsondisk" target="_blank" >grantjenks.com</a><br/> <strong>Git Code Archaeology Charts</strong>: <a href="https://koaning.github.io/gitcharts/#django/versioned" target="_blank" >koaning.github.io</a><br/> <strong>Talk Python Cache Admin UI</strong>: <a href="https://blobs.talkpython.fm/talk-python-cache-admin.png?cache_id=cd0d7f" target="_blank" >blobs.talkpython.fm</a><br/> <strong>Litestream SQLite streaming</strong>: <a href="https://litestream.io?featured_on=talkpython" target="_blank" >litestream.io</a><br/> <strong>Plash hosting</strong>: <a href="https://pla.sh?featured_on=talkpython" target="_blank" >pla.sh</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=ze7N_RE9KU0" target="_blank" >youtube.com</a><br/> <strong>Episode #534 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/534/diskcache-your-secret-python-perf-weapon#takeaways-anchor" target="_blank" >talkpython.fm/534</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/534/diskcache-your-secret-python-perf-weapon" 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>
January 12, 2026
Real Python
Python's deque: Implement Efficient Queues and Stacks
You can use Python’s deque for efficient appends and pops at both ends of a sequence-like data type. These capabilities are critical when you need to implement queue and stack data structures that operate efficiently even under heavy workloads.
In this tutorial, you’ll learn how deque works, when to use it over a list, and how to apply it in real code.
By the end of this tutorial, you’ll understand that:
dequeinternally uses a doubly linked list, so end operations are O(1) while random indexing is O(n).- You can build a FIFO queue with
.append()and.popleft(), and a LIFO stack with.append()and.pop(). dequesupports indexing but doesn’t support slicing.- Passing a value to
maxlencreates a bounded deque that drops items from the opposite end when full. - In CPython,
.append(),.appendleft(),.pop(),.popleft(), andlen()are thread-safe for multithreaded use.
Up next, you’ll get started with deque, benchmark it against list, and explore how it shines in real-world use cases, such as queues, stacks, history buffers, and thread-safe producer-consumer setups.
Get Your Code: Click here to download the free sample code that shows you how to implement efficient queues and stacks with Python’s deque.
Take the Quiz: Test your knowledge with our interactive “Python's deque: Implement Efficient Queues and Stacks” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python's deque: Implement Efficient Queues and StacksUse Python's deque for fast queues and stacks. Refresh end operations, maxlen rollover, indexing limits, and thread-safe methods.
Get Started With Python’s deque
Appending to and popping from the right end of a Python list are efficient operations most of the time. Using the Big O notation for time complexity, these operations are O(1). However, when Python needs to reallocate memory to grow the underlying list to accept new items, these operations slow down and can become O(n).
In contrast, appending and popping items from the left end of a Python list are always inefficient and have O(n) time complexity.
Because Python lists provide both operations with the .append() and .pop() methods, you can use them as stacks and queues. However, the performance issues you saw before can significantly impact the overall performance of your applications.
Python’s deque was the first data type added to the collections module back in Python 2.4. This data type was specially designed to overcome the efficiency problems of .append() and .pop() in Python lists.
A deque is a sequence-like data structure designed as a generalization of stacks and queues. It supports memory-efficient and fast append and pop operations on both ends.
Note: The word deque is pronounced as “deck.” The name stands for double-ended queue.
Append and pop operations on both ends of a deque object are stable and equally efficient because deques are implemented as a doubly linked list. Additionally, append and pop operations on deques are thread-safe and memory-efficient. These features make deques particularly useful for creating custom stacks and queues in Python.
Deques are also a good choice when you need to keep a list of recently seen items, as you can restrict the maximum length of your deque. By setting a maximum length, once a deque is full, it automatically discards items from one end when you append new items to the opposite end.
Here’s a summary of the main features of deque:
- Stores items of any data type
- Is a mutable data type
- Supports membership operations with the
inoperator - Supports indexing, like in
a_deque[i] - Doesn’t support slicing, like in
a_deque[0:2] - Supports built-in functions that operate on sequences and iterables, such as
len(),sorted(),reversed(), and more - Doesn’t support in-place sorting
- Supports normal and reverse iteration
- Supports pickling with
pickle - Supports fast, memory-efficient, and thread-safe pop and append operations on both ends
To create deques, you just need to import deque from collections and call it with an optional iterable as an argument:
>>> from collections import deque
>>> # Create an empty deque
>>> deque()
deque([])
>>> # Use different iterables to create deques
>>> deque((1, 2, 3, 4))
deque([1, 2, 3, 4])
>>> deque([1, 2, 3, 4])
deque([1, 2, 3, 4])
>>> deque(range(1, 5))
deque([1, 2, 3, 4])
>>> deque("abcd")
deque(['a', 'b', 'c', 'd'])
>>> numbers = {"one": 1, "two": 2, "three": 3, "four": 4}
>>> deque(numbers.keys())
deque(['one', 'two', 'three', 'four'])
>>> deque(numbers.values())
deque([1, 2, 3, 4])
>>> deque(numbers.items())
deque([('one', 1), ('two', 2), ('three', 3), ('four', 4)])
If you instantiate deque without providing an iterable as an argument, then you get an empty deque. If you provide an iterable, then deque initializes the new instance with data from it. The initialization goes from left to right using deque.append().
The deque initializer takes the following two optional arguments:
iterableholds an iterable that provides the initialization data.maxlenholds an integer number that specifies the maximum length of the deque.
As mentioned previously, if you don’t supply an iterable, then you get an empty deque. If you provide a value to maxlen, then your deque will only store up to maxlen items.
Finally, you can also use unordered iterables, such as sets, to initialize your deques. In those cases, you won’t have a predefined order for the items in the final deque.
Read the full article at https://realpython.com/python-deque/ »
[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]
The Python Coding Stack
Need a Constant in Python? Enums Can Come in Useful
Python doesn’t have constants. You probably learnt this early on when learning Python. Unlike many other programming languages, you can’t define a constant in Python. All variables are variable!
“Ah, but there are immutable types.”
Sure, you can have an object that doesn’t change throughout its lifetime. But you can’t have a reference to it that’s guaranteed not to change. The identifier (variable name) you use to refer to this immutable type can easily switch to refer to something else.
“How about using all caps for the identifier. Doesn’t that make it a constant?”
No, it doesn’t. That’s just a convention you use to show your intent as a programmer that an identifier refers to a value that shouldn’t change. But nothing prevents that value from changing.
Here’s an all-uppercase identifier that refers to an immutable object:
The identifier is all caps. The object is a tuple, which is immutable. Recall that you don’t need parentheses to create a tuple—the comma is sufficient.
So, you use an all-uppercase identifier for an immutable object. But that doesn’t stop you from changing the value of FIXED_LOCATION:
Neither using an immutable object nor using uppercase identifiers prevents you from changing this value!
So, Python doesn’t have constants. But there are tools you can use to mimic constant behaviour depending on the use case you need. In this article I’ll explore one of these: Enums.
All The Python Coding Place video courses are included in a single, cost-effective bundle. The courses cover beginner and intermediate level courses, and you also get access to a members-only forum.
Jargon Corner: Enum is short for enumeration, and you’ll see why soon. But don’t confuse this with the built-in enumerate(), which does something else. See Parkruns, Python’s enumerate and zip, and Why Python Loops Are Different from Other Languages • [Note: This is a Club post] for more on enumerate().
Let’s revisit our friend Alex from an article from a short while ago: “AI Coffee” Grand Opening This Monday. This article explored the program Alex used in his new coffee shop and how the function signature changed over time to minimise confusion and errors when using it. It’s a fun article about all the various types and styles of parameters and arguments you can have in Python functions.
But it didn’t address another potential source of error when using this code. So let’s look at a simple version of the brew_coffee() function Alex used to serve his coffee-drinking customers:
When you call the function, you pass the coffee you want to this function:
And elsewhere in the code, these coffees are defined in a dictionary:
If you’ve written code like this in the past, you’ll know that it’s rather annoying—and error-prone—to keep using the strings with the coffee names wherever you need to refer to a specific coffee, such as when passing the coffees to brew_coffee().
The names of the coffees and the parameters that define them do not change. They’re constant. It’s a shame Python doesn’t have constants, you may think.
But it has enums…
The CoffeeType enum contains seven members. Each member has a name and a value. By convention, you use all-uppercase names for the members since they represent constants. And these enum members behave like constants:
When you attempt to reassign a value to a member, Python raises an exception:
Traceback (most recent call last):
File ..., line 12, in <module>
CoffeeType.ESPRESSO = 10
^^^^^^^^^^^^^^^^^^^
...
AttributeError: cannot reassign member ‘ESPRESSO’The member names are also contained within the namespace of the Enum class—you use CoffeeType.ESPRESSO rather than just ESPRESSO outside the Enum class definition. So, you get autocomplete, refactor-friendly names, and fewer silent typos. With raw strings, "capuccino" (with a single “p”) can sneak into your code, and nothing complains until a customer is already waiting at the counter.
For these enum members to act as constants, their names must be unique. You can’t have the same name appear more than once:
You include ESPRESSO twice with different values. But this raises an exception:
Traceback (most recent call last):
File ..., line 3, in <module>
...
ESPRESSO = 8
^^^^^^^^
...
TypeError: ‘ESPRESSO’ already defined as 1That’s good news. Otherwise, these enum members wouldn’t be very useful as constants.
However, you can have an alias. You can have more than one member sharing the same value:
The members MACCHIATO and ESPRESSO_MACCHIATO both have the value 4. Therefore, they represent the same item. They’re different names for the same coffee:
Note that Python always displays the first member associated with a value:
CoffeeType.MACCHIATOThe output says CoffeeType.MACCHIATO even though you pass CoffeeType.ESPRESSO_MACCHIATO to print().
Incidentally, if you don’t want to have aliases, you can use the @unique decorator when defining the enum class.
You can also access the name and value of an enum member:
Here’s the output from this code:
CoffeeType.ESPRESSO
ESPRESSO
1The .name attribute is a string, and the .value attribute is an integer in this case:
Here’s the output when you display the types:
<enum ‘CoffeeType’>
<class ‘str’>
<class ‘int’>You’ll often use integers as values for enum members—that’s why they’re called enumerations. But you don’t have to:
The values are now also strings:
CoffeeType.ESPRESSO
ESPRESSO
espressoYou can use these enum members instead of strings wherever you need to refer to each coffee type:
…and again when you call brew_coffee():
Now you have a safer, neater, and more robust way to handle the coffee types... and treat them as constants.
A Bit More • StrEnum and IntEnum
Let’s add some code to brew_coffee():
This version is almost fine. But here’s a small problem:
Brewing a CoffeeType.CORTADO with 30ml of coffee and
60ml of milk. Strength level: 2The output displays CoffeeType.CORTADO since coffee_type refers to an enum member. You’d like the output to just show the name of the coffee! Of course, you can use the .value attribute any time you need to fetch the string.
However, to make your coding simpler and more readable, you can ensure that the enum members are also strings themselves without having to rely on one of their attributes. You can use StrEnum instead of Enum:
Members of a StrEnum also inherit all the string methods, such as:
You call the string method .title() directly on the StrEnum member:
MacchiatoThere’s also an IntEnum that can be useful when you want your enum members to act as integers. Let’s replace the coffee strength values, which are currently integers, with IntEnum members:
You could use a standard Enum in this case. But using an IntEnum allows you to manipulate its members directly as integers should you need to do so. Here’s an example:
This code is equivalent to printing 3 + 1. You wouldn’t be able to do this with enums unless you use the .value attributes.
And A Couple More Things About Enums
Let’s explore a couple of other useful enum features before we wrap up this article.
An enum class is iterable. Here are all the coffee types in a for loop:
Note that CoffeeType is the class name. But it’s an enum (a StrEnum in this case), so it’s iterable:
Brewing a espresso with 30ml of coffee and 0ml of milk. Strength level: 3
Brewing a latte with 30ml of coffee and 150ml of milk. Strength level: 1
Brewing a cappuccino with 30ml of coffee and 100ml of milk. Strength level: 2
Brewing a macchiato with 30ml of coffee and 10ml of milk. Strength level: 3
Brewing a flat_white with 30ml of coffee and 120ml of milk. Strength level: 2
Brewing a ristretto with 20ml of coffee and 0ml of milk. Strength level: 4
Brewing a cortado with 30ml of coffee and 60ml of milk. Strength level: 2I’ll let you sort out the text displayed to make sure you get ‘an espresso’ when brewing an espresso and to remove the underscore in the flat white!
And there will be times when you don’t care about the value of an enum member. You just want to use an enum to give your constants a consistent name. In this case, you can use the automatic value assignment:
Python assigns integers incrementally in the order you define the members for Enum classes. Note that these start from 1, not 0.
The same integers are used if you use IntEnum classes. However, when you use StrEnum classes, Python behaves differently since the values should be strings in this case:
The values are now the lowercase strings representing the members’ names.
Of course, the default values you get when you use auto() may be the values you need, after all. This is the case for both enums you created in this article, CoffeeType and CoffeeStrength :
Using auto() when appropriate makes it easier to write your code and expand it later if you need to add more enum members.
Final Words
You can get by without ever using enums. But there are many situations where you’d love to reach for a constant, and an enum will do just fine. Sure, Python doesn’t have constants. But it has enums!
Code in this article uses Python 3.14
The code images used in this article are created using Snappify. [Affiliate link]
Join The Club, the exclusive area for paid subscribers for more Python posts, videos, a members’ forum, and more.
You can also support this publication by making a one-off contribution of any amount you wish.
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com
Appendix: Code Blocks
Code Block #1
FIXED_LOCATION = 51.75, 0.34
Code Block #2
FIXED_LOCATION
# (51.75, 0.34)
FIXED_LOCATION = "Oops!"
FIXED_LOCATION
# 'Oops!'
Code Block #3
def brew_coffee(coffee_type):
# Actual code goes here...
# It's not relevant for this article
Code Block #4
brew_coffee("espresso")
brew_coffee("cappuccino")
Code Block #5
coffee_types = {
"espresso": {"strength": 3, "coffee_amount": 30, "milk_amount": 0},
"latte": {"strength": 1, "coffee_amount": 30, "milk_amount": 150},
"cappuccino": {"strength": 2, "coffee_amount": 30, "milk_amount": 100},
"macchiato": {"strength": 3, "coffee_amount": 30, "milk_amount": 10},
"flat_white": {"strength": 2, "coffee_amount": 30, "milk_amount": 120},
"ristretto": {"strength": 4, "coffee_amount": 20, "milk_amount": 0},
"cortado": {"strength": 2, "coffee_amount": 30, "milk_amount": 60},
}
Code Block #6
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = 1
LATTE = 2
CAPPUCCINO = 3
MACCHIATO = 4
FLAT_WHITE = 5
RISTRETTO = 6
CORTADO = 7
Code Block #7
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = 1
LATTE = 2
CAPPUCCINO = 3
MACCHIATO = 4
FLAT_WHITE = 5
RISTRETTO = 6
CORTADO = 7
CoffeeType.ESPRESSO = 10
Code Block #8
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = 1
LATTE = 2
CAPPUCCINO = 3
MACCHIATO = 4
FLAT_WHITE = 5
RISTRETTO = 6
CORTADO = 7
ESPRESSO = 8
Code Block #9
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = 1
LATTE = 2
CAPPUCCINO = 3
MACCHIATO = 4
FLAT_WHITE = 5
RISTRETTO = 6
CORTADO = 7
ESPRESSO_MACCHIATO = 4
Code Block #10
print(CoffeeType.ESPRESSO_MACCHIATO)
Code Block #11
# ...
print(CoffeeType.ESPRESSO)
print(CoffeeType.ESPRESSO.name)
print(CoffeeType.ESPRESSO.value)
Code Block #12
# ...
print(type(CoffeeType.ESPRESSO))
print(type(CoffeeType.ESPRESSO.name))
print(type(CoffeeType.ESPRESSO.value))
Code Block #13
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = "espresso"
LATTE = "latte"
CAPPUCCINO = "cappuccino"
MACCHIATO = "macchiato"
FLAT_WHITE = "flat_white"
RISTRETTO = "ristretto"
CORTADO = "cortado"
print(CoffeeType.ESPRESSO)
print(CoffeeType.ESPRESSO.name)
print(CoffeeType.ESPRESSO.value)
Code Block #14
# ...
coffee_types = {
CoffeeType.ESPRESSO: {"strength": 3, "coffee_amount": 30, "milk_amount": 0},
CoffeeType.LATTE: {"strength": 1, "coffee_amount": 30, "milk_amount": 150},
CoffeeType.CAPPUCCINO: {"strength": 2, "coffee_amount": 30, "milk_amount": 100},
CoffeeType.MACCHIATO: {"strength": 3, "coffee_amount": 30, "milk_amount": 10},
CoffeeType.FLAT_WHITE: {"strength": 2, "coffee_amount": 30, "milk_amount": 120},
CoffeeType.RISTRETTO: {"strength": 4, "coffee_amount": 20, "milk_amount": 0},
CoffeeType.CORTADO: {"strength": 2, "coffee_amount": 30, "milk_amount": 60},
}
Code Block #15
# ...
brew_coffee(CoffeeType.CORTADO)
Code Block #16
# ...
def brew_coffee(coffee_type):
coffee_details = coffee_types.get(coffee_type)
if not coffee_details:
print("Unknown coffee type!")
return
print(
f"Brewing a {coffee_type} "
f"with {coffee_details['coffee_amount']}ml of coffee "
f"and {coffee_details['milk_amount']}ml of milk. "
f"Strength level: {coffee_details['strength']}"
)
brew_coffee(CoffeeType.CORTADO)
Code Block #17
from enum import StrEnum
class CoffeeType(StrEnum):
ESPRESSO = "espresso"
LATTE = "latte"
CAPPUCCINO = "cappuccino"
MACCHIATO = "macchiato"
FLAT_WHITE = "flat_white"
RISTRETTO = "ristretto"
CORTADO = "cortado"
# ...
def brew_coffee(coffee_type):
coffee_details = coffee_types.get(coffee_type)
if not coffee_details:
print("Unknown coffee type!")
return
print(
f"Brewing a {coffee_type} "
f"with {coffee_details['coffee_amount']}ml of coffee "
f"and {coffee_details['milk_amount']}ml of milk. "
f"Strength level: {coffee_details['strength']}"
)
brew_coffee(CoffeeType.CORTADO)
Code Block #18
print(CoffeeType.MACCHIATO.title())
Code Block #19
# ...
class CoffeeStrength(IntEnum):
WEAK = 1
MEDIUM = 2
STRONG = 3
EXTRA_STRONG = 4
coffee_types = {
CoffeeType.ESPRESSO: {
"strength": CoffeeStrength.STRONG,
"coffee_amount": 30,
"milk_amount": 0,
},
CoffeeType.LATTE: {
"strength": CoffeeStrength.WEAK,
"coffee_amount": 30,
"milk_amount": 150,
},
CoffeeType.CAPPUCCINO: {
"strength": CoffeeStrength.MEDIUM,
"coffee_amount": 30,
"milk_amount": 100,
},
# ... and so on...
}
# ...
Code Block #20
print(CoffeeStrength.STRONG + CoffeeStrength.WEAK)
Code Block #21
# ...
for coffee in CoffeeType:
brew_coffee(coffee)
Code Block #22
from enum import Enum, auto
class Test(Enum):
FIRST = auto()
SECOND = auto()
Test.FIRST
# <Test.FIRST: 1>
Test.SECOND
# <Test.SECOND: 2>
Code Block #23
from enum import StrEnum, auto
class Test(StrEnum):
FIRST = auto()
SECOND = auto()
Test.FIRST
# <Test.FIRST: 'first'>
Test.SECOND
# <Test.SECOND: 'second'>
Code Block #24
# ...
class CoffeeType(StrEnum):
ESPRESSO = auto()
LATTE = auto()
CAPPUCCINO = auto()
MACCHIATO = auto()
FLAT_WHITE = auto()
RISTRETTO = auto()
CORTADO = auto()
class CoffeeStrength(IntEnum):
WEAK = auto()
MEDIUM = auto()
STRONG = auto()
EXTRA_STRONG = auto()
# ...
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com
Python Bytes
#465 Stack Overflow is Cooked
<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://github.com/productdevbook/port-killer?featured_on=pythonbytes">port-killer</a></strong></li> <li><strong><a href="https://iscinumpy.dev/post/packaging-faster/?featured_on=pythonbytes">How we made Python's packaging library 3x faster</a></strong></li> <li><strong>CodSpeed</strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=waNYGS7u8Ts' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="465">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 Training</strong></a></li> <li><a href="https://courses.pythontest.com/p/the-complete-pytest-course?featured_on=pythonbytes"><strong>The Complete pytest Course</strong></a></li> <li><a href="https://www.patreon.com/pythonbytes"><strong>Patreon Supporters</strong></a></li> </ul> <p><strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href="https://fosstodon.org/@mkennedy">@mkennedy@fosstodon.org</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">@mkennedy.codes</a> (bsky)</li> <li>Brian: <a href="https://fosstodon.org/@brianokken">@brianokken@fosstodon.org</a> / <a href="https://bsky.app/profile/brianokken.bsky.social?featured_on=pythonbytes">@brianokken.bsky.social</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">@pythonbytes@fosstodon.org</a> / <a href="https://bsky.app/profile/pythonbytes.fm">@pythonbytes.fm</a> (bsky)</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>Monday</strong> at 11am PT. Older video versions available there too.</p> <p>Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it.</p> <p><strong>Michael #1: <a href="https://github.com/productdevbook/port-killer?featured_on=pythonbytes">port-killer</a></strong></p> <ul> <li>A powerful cross-platform port management tool for developers.</li> <li>Monitor ports, manage Kubernetes port forwards, integrate Cloudflare Tunnels, and kill processes with one click.</li> <li>Features: <ul> <li>🔍 Auto-discovers all listening TCP ports</li> <li>⚡ One-click process termination (graceful + force kill)</li> <li>🔄 Auto-refresh with configurable interval</li> <li>🔎 Search and filter by port number or process name</li> <li>⭐ Favorites for quick access to important ports</li> <li>👁️ Watched ports with notifications</li> <li>📂 Smart categorization (Web Server, Database, Development, System)</li> </ul></li> </ul> <p><strong>Brian #2: <a href="https://iscinumpy.dev/post/packaging-faster/?featured_on=pythonbytes">How we made Python's packaging library 3x faster</a></strong></p> <ul> <li>Henry Schreiner</li> <li>Some very cool graphs demonstrating some benchmark data.</li> <li>And then details about how various speedups <ul> <li>each being 2-37% faster</li> <li>the total adding up to about 3x speedup, or shaving 2/3 of the time.</li> </ul></li> <li>These also include nice write-ups about why the speedups were chosen.</li> <li>If you are trying to speed up part of your system, this would be good article to check out.</li> </ul> <p><strong>Michael #3</strong>: AI’s Impact on dev companies</p> <ul> <li><strong>On TailwindCSS</strong>: <a href="https://simonwillison.net/2026/Jan/7/adam-wathan/#atom-everything">via Simon</a> <ul> <li>Tailwind is growing faster than ever and is bigger than it has ever been</li> <li>Its revenue is down close to 80%.</li> <li>75% of the people on our engineering team lost their jobs here yesterday because of the brutal impact AI has had on our business.</li> <li>“We had 6 months left”</li> <li>Listen to the founder: “<a href="https://adams-morning-walk.transistor.fm/episodes/we-had-six-months-left?featured_on=pythonbytes">A Morning Walk</a>”</li> <li>Super insightful video: <a href="https://www.youtube.com/watch?v=tSgch1vcptQ&pp=0gcJCU0KAYcqIYzv">Tailwind is in DEEP trouble</a></li> </ul></li> <li><strong>On Stack Overflow</strong>: <a href="https://www.youtube.com/watch?v=Gy0fp4Pab0g">See video</a>. <ul> <li>SO was founded around 2009, first month had 3,749 questions</li> <li>December, SO had 3,862 questions asked</li> <li>Most of its live it had 200,000 questions per month</li> <li>That is a 53x drop!</li> </ul></li> </ul> <p><strong>Brian #4: CodSpeed</strong></p> <ul> <li>“CodSpeed integrates into dev and CI workflows to measure performance, detect regressions, and enable actionable optimizations.”</li> <li>Noticed it while looking through the <a href="https://github.com/fastapi/fastapi/blob/master/.github/workflows/test.yml?featured_on=pythonbytes">GitHub workflows for FastAPI</a></li> <li>Free for small teams and open-source projects</li> <li>Easy to integrate with Python by marking tests with <code>@pytest.mark.benchmark</code></li> <li>They’ve releases a GitHub action to incorporate benchmarking in CI workflows</li> </ul> <p><strong>Extras</strong></p> <p>Brian:</p> <ul> <li>Part 2 of <a href="https://courses.pythontest.com/lean-tdd/?featured_on=pythonbytes">Lean TDD</a> released this morning, “Lean TDD Practices”, which has 9 mini chapters.</li> </ul> <p>Michael:</p> <ul> <li>Our Docker build just broke because of <a href="https://mkennedy.codes/posts/devops-python-supply-chain-security/?featured_on=pythonbytes">the supply chain techniques from last week</a> (that’s a good thing!). Not a real issue, but really did catch an open CVE.</li> <li><a href="https://instatunnel.my/blog/the-1mb-password-crashing-backends-via-hashing-exhaustion?featured_on=pythonbytes">Long passwords are bad now</a>? ;)</li> </ul> <p><strong>Joke: <a href="https://x.com/PR0GRAMMERHUM0R/status/2008644769799434688?featured_on=pythonbytes">Check out my app</a>!</strong></p>
Python GUIs
What does @pyqtSlot() do? — Is the pyqtSlot decorator even necessary?
When working with Qt slots and signals in PyQt6 you will discover the @pyqtSlot decorator. This decorator is used to mark a Python function or method as a slot to which a Qt signal can be connected. However, as you can see in our signals and slots tutorials you don't have to use this. Any Python function or method can be used, normally, as a slot for a Qt signals. But elsewhere, in our threading tutorials we do use it.
What's going on here?
- Why do you sometimes use
@pyqtSlotbut usually not? - What happens when you omit it?
- Are there times when it is required?
What does the documentation say?
The PyQt6 documentation has a good explanation:
Although PyQt6 allows any Python callable to be used as a slot when connecting signals, it is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it. PyQt6 provides the
pyqtSlot()function decorator to do this.Connecting a signal to a decorated Python method has the advantage of reducing the amount of memory used and is slightly faster.
From the above we see that:
- Any Python callable can be used as a slot when connecting signals.
- It is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it.
- There is a side-benefit in that marking a function or method with
pyqtSlot()reduces the amount of memory used, and makes the slot faster.
When is it necessary?
Sometimes necessary is a bit vague. In practice the only situation where you need to use pyqtSlot decorators is when working with threads. This is because of a difference in how signal connections are handled in decorated vs. undecorated slots.
- If you decorate a method with
@pyqtSlotthen that slot is created as a native Qt slot, and behaves identically - If you don't decorate the method then PyQt6 will create a "proxy" object wrapper which provides a native slot to Qt
In normal use this is fine, aside from the performance impact (see below). But when working with threads, there is a complication: is the proxy object created on the GUI thread or on the runner thread. If it ends up on the wrong thread, this can lead to segmentation faults. Using the pyqtSlot decorator side-steps this issue, because no proxy is created.
When updating my PyQt6 book I wondered -- is this still necessary?! -- and tested removing it from the examples. Many examples continue to work, but some failed. To be safe, use pyqtSlot decorators on your QRunnable.run methods.
What about performance?
The PyQt6 documentation notes that using native slots "has the advantage of reducing the amount of memory used and is slightly faster". But how much faster is it really, and does decorating slots actually save much memory?
We can test this directly by using this script from Oliver L Schoenborn. Updating for PyQt6 (replace PyQt5 with PyQt6 and it will work as-is) and running this we get the following results:
See the original results for PyQt5 for comparison.
First the results for the speed of emitting signals when connected to a decorated slot, vs non-decorated.
Raw slot mean, stddev: 0.578 0.024
Pyqt slot mean, stddev: 0.587 0.021
Percent gain with pyqtSlot: -2 %
The result shows pyqtSlot as 2% slower, but this is negligible (the original data on PyQt5 also showed no difference). So, using pyqtSlot will have no noticeable impact on the speed of signal handling in your applications.
Next are the results for establishing connections. This shows the speed, and memory usage of connecting to decorated vs. non-decorated slots.
Comparing mem and time required to create 10000000 connections, 1000 times
Measuring for 1000000 connections
# connects mem (bytes) time (sec)
Raw : 1000000 949186560 (905MB) 9.02
Pyqt Slot : 1000000 48500736 ( 46MB) 1.52
Ratios : 20 6
The results show that decorated slots are about 6x faster to connect to. This sounds like a big difference, but it would only be noticeable if an application was connecting a considerable number of signals. Based on these numbers, if you connected 100 signals the total execution time difference would be 0.9 ms vs 0.15 ms. This is negligible, not to mention imperceptible.
Perhaps more significant is that using raw connections uses 20x the memory of decorated connections. Again though, bear in mind that for a more realistic upper limit of connections (100) the actual difference here is 0.09MB vs 0.004MB.
The bottom line: don't expect any dramatic improvements in performance or memory usage from using slot decorators, unless you're working with insanely large numbers of signals or making regular connections you won't see any difference at all. That said, decorating your slots is an easy win if you need it.
Are there any other reasons to decorate a slot?
In Qt signals can be used to transmit more than one type of data by overloading signals and slots with different types.
For example, with the following code the my_slot_fn will only receive signals which match the signature of two int values.
@pyqtSlot(int, int)
def my_slot_fn(a, b):
pass
This is a legacy of Qt5 and not recommended in new code. In Qt6 all of these signals have been replaced with separate signals with distinct names for different types. I recommend you follow the same approach in your own code for the sake of simplicity.
Conclusion
The pyqtSlot decorator can be used to mark Python functions or methods as Qt slots. This decorator is only required on slots which may be connected to across threads, for example the run method of QRunnable objects. For all other slots it can be omitted. There is a very small performance benefit to using it, which you may want to consider when your application makes a large number of signal/slot connections.
For an in-depth guide to building Python GUIs with PyQt5 see my book, Create GUI Applications with Python & Qt5.
Zato Blog
SSH API Service in Python
SSH API Service in Python
This is a quick guide on how to turn SSH commands into a REST API service. The use-case may be remote administration of devices or equipment that does not offer a REST interface or making sure that access to SSH commands is restricted to selected external REST-based API clients only.
Python
The first thing needed is code of the service that will connect to SSH servers. Below is a service doing just that - it receives name of the command to execute and host to run in on, translating stdout and stderr of SSH commands into response documents which Zato in turn serializes to JSON.
# -*- coding: utf-8 -*-
# stdlib
from traceback import format_exc
# Zato
from zato.server.service import Service
class SSHInvoker(Service):
""" Accepts an SSH command to run on a remote host and returns its output to caller.
"""
# A list of elements that we expect on input
input = 'host', 'command'
# A list of elements that our responses will contain
output = 'is_ok', 'cid', '-stdout', '-stderr'
def handle(self):
# Local aliases
host = self.request.input.host
command = self.request.input.command
# Correlation ID is always returned
self.response.payload.cid = self.cid
try:
# Build the full command
full_command = f'ssh {host} {command}'
# Run the command and collect output
output = self.commands.invoke(full_command)
# Assign both stdout and stderr to response
self.response.payload.stdout = output.stdout
self.response.payload.stderr = output.stderr
except Exception:
# Catch any exception and log it
self.logger.warn('Exception caught (%s), e:`%s', self.cid, format_exc())
# Indicate an error
self.response.payload.is_ok = False
else:
# Everything went fine
self.response.payload.is_ok = True
Dashboard
In the Zato Dashboard, let's go ahead and create an HTTP Basic Auth definition that a remote API client will authenticate against:

Now, the SSH service can be mounted on a newly created REST channel - note the security definition used and that data format is set to JSON. We can skip all the other details such as caching or rate limiting, for illustration purposes, this is not needed.


Usage
At this point, everything is ready to use. We could make it accessible to external API clients but, for testing purposes, let's simply invoke our SSH API gateway service from the command line:
$ curl "api:password@localhost:11223/api/ssh" -d \
'{"host":"localhost", "command":"uptime"}'
{
"is_ok": true,
"cid": "27406f29c66c2ab6296bc0c0",
"stdout": " 09:45:42 up 37 min, 1 user, load average: 0.14, 0.27, 0.18\n"}
$
And this completes it - the service is deployed and made accessible via a REST channel that can be invoked using JSON. Any command can be sent to any host and their output will be returned to API callers in JSON responses.
More resources
➤ Python API integration tutorials
➤ What is an integration platform?
➤ Python Integration platform as a Service (iPaaS)
➤ What is an Enterprise Service Bus (ESB)? What is SOA?
➤ Open-source iPaaS in Python
Wingware
Wing Python IDE Version 11.0.7 - January 12, 2026
Wing Python IDE version 11.0.7 has been released. It improves performance of Search in Files on some machines, fixes using stdout.writelines in unit tests run from the Testing tool, reduces CPU used by rescanning for package managers, and fixes analysis failures on incorrect # type: comments.

Downloads
Wing 10 and earlier versions are not affected by installation of Wing 11 and may be installed and used independently. However, project files for Wing 10 and earlier are converted when opened by Wing 11 and should be saved under a new name, since Wing 11 projects cannot be opened by older versions of Wing.
New in Wing 11
Improved AI Assisted Development
Wing 11 improves the user interface for AI assisted development by introducing two separate tools AI Coder and AI Chat. AI Coder can be used to write, redesign, or extend code in the current editor. AI Chat can be used to ask about code or iterate in creating a design or new code without directly modifying the code in an editor.
Wing 11's AI assisted development features now support not just OpenAI but also Claude, Grok, Gemini, Perplexity, Mistral, Deepseek, and any other OpenAI completions API compatible AI provider.
This release also improves setting up AI request context, so that both automatically and manually selected and described context items may be paired with an AI request. AI request contexts can now be stored, optionally so they are shared by all projects, and may be used independently with different AI features.
AI requests can now also be stored in the current project or shared with all projects, and Wing comes preconfigured with a set of commonly used requests. In addition to changing code in the current editor, stored requests may create a new untitled file or run instead in AI Chat. Wing 11 also introduces options for changing code within an editor, including replacing code, commenting out code, or starting a diff/merge session to either accept or reject changes.
Wing 11 also supports using AI to generate commit messages based on the changes being committed to a revision control system.
You can now also configure multiple AI providers for easier access to different models.
For details see AI Assisted Development under Wing Manual in Wing 11's Help menu.
Package Management with uv
Wing Pro 11 adds support for the uv package manager in the New Project dialog and the Packages tool.
For details see Project Manager > Creating Projects > Creating Python Environments and Package Manager > Package Management with uv under Wing Manual in Wing 11's Help menu.
Improved Python Code Analysis
Wing 11 makes substantial improvements to Python code analysis, with better support for literals such as dicts and sets, parametrized type aliases, typing.Self, type of variables on the def or class line that declares them, generic classes with [...], __all__ in *.pyi files, subscripts in typing.Type and similar, type aliases, type hints in strings, type[...] and tuple[...], @functools.cached_property, base classes found also in .pyi files, and typing.Literal[...].
Updated Localizations
Wing 11 updates the German, French, and Russian localizations, and introduces a new experimental AI-generated Spanish localization. The Spanish localization and the new AI-generated strings in the French and Russian localizations may be accessed with the new User Interface > Include AI Translated Strings preference.
Improved diff/merge
Wing Pro 11 adds floating buttons directly between the editors to make navigating differences and merging easier, allows undoing previously merged changes, and does a better job managing scratch buffers, scroll locking, and sizing of merged ranges.
For details see Difference and Merge under Wing Manual in Wing 11's Help menu.
Other Minor Features and Improvements
Wing 11 also adds support for Python 3.14, improves the custom key binding assignment user interface, adds a Files > Auto-Save Files When Wing Loses Focus preference, warns immediately when opening a project with an invalid Python Executable configuration, allows clearing recent menus, expands the set of available special environment variables for project configuration, and makes a number of other bug fixes and usability improvements.
Changes and Incompatibilities
Since Wing 11 replaced the AI tool with AI Coder and AI Chat, and AI configuration is completely different than in Wing 10, you will need to reconfigure your AI integration manually in Wing 11. This is done with Manage AI Providers in the AI menu. After adding the first provider configuration, Wing will set that provider as the default. You can switch between providers with Switch to Provider in the AI menu.
If you have questions, please don't hesitate to contact us at support@wingware.com.
January 10, 2026
EuroPython
Humans of EuroPython: Jakub Červinka
EuroPython wouldn’t exist if it weren’t for all the volunteers who put in countless hours to organize it. Whether it’s contracting the venue, selecting and confirming talks & workshops or coordinating with speakers, hundreds of hours of loving work have been put into making each edition the best one yet.
Read our latest interview with Jakub Červinka, a member of the EuroPython 2025 Operations Team and organizer of PyConCZ 2026.
Thank you for your service to EuroPython, Jakub!
Jakub Červinka, member of the Operations Team at EuroPython 2025EP: What first inspired you to volunteer for EuroPython?
The community has always been the biggest draw for me. Having volunteered at our local Python conference previously, I already knew how rewarding it is to be part of the organizing team. When the opportunity to join EuroPython came up, I jumped at it without a second thought. I really like connecting with organizers, speakers, and attendees from across the continent.
EP: What&aposs one task you handled that attendees might not realize happens behind the scenes at EuroPython?
One year I took on the role of “designated driver”, essentially the person who handles the last-minute, ad-hoc tasks that arise during the conference. It ranged from running out to buy a cart full of hygiene products for the bathrooms, to hauling cases of bottled water when we were about to run dry, to picking up emergency prints on one of the hottest days of the year. These are the kinds of small but critical jobs that keep everything running smoothly, and I enjoy making sure they get done.
EP: How did volunteering for EuroPython impact your relationships within the community?
In the best possible way. Over the years, I’ve built lasting friendships, met people I had only known from online talks and tutorials, and had the chance to become a familiar face in the community myself. Every EuroPython and every local conference strengthens those connections and leaves you with renewed energy and inspiration to keep contributing.
EP: What&aposs one thing you took away from the experience that you still use today?
The importance of recognition and appreciation. A simple “thank you” or “great job” from an attendee can mean a lot to volunteers. We’re doing important work, but it’s not our paid job. That experience has made me much more intentional about expressing gratitude to everyone who helps, whether they’re fellow volunteers, staff, or people in service roles.
EP: Do you have any tips for first-time EuroPython volunteers?
Don’t be afraid to ask questions or offer help, there’s always something that needs doing, and everyone can contribute in their own way. Keep an eye out for small improvements you could suggest, introduce yourself to people, and most importantly, enjoy the experience. Volunteering is as much about building relationships and having fun as it is about getting tasks done.
EP: Thank you, Jakub!
January 09, 2026
Mike Driscoll
How to Switch to ty from Mypy
Python has supported type hinting for quite a few versions now, starting way back in 3.5. However, Python itself does not enforce type checking. Instead, you need to use an external tool or IDE. The first and arguably most popular is mypy.
Microsoft also has a Python type checker that you can use in VS Code called Pyright, and then there’s the lesser-known Pyrefly type checker and language server.
The newest type checker on the block is Astral’s ty, the maker of Ruff. Ty is another super-fast Python utility written in Rust.
In this article, you will learn how to switch your project to use ty locally and in GitHub Actions.
Installation
You can run ty with uvx if you do not want to install it by using the following command in your terminal: uvx ty
To install ty with uv, run the following:
uv tool install ty@latest
If you do not want to use uv, you can use the standalone installer. Instructions vary depending on your platform, so it is best to refer to the documentation for the latest information.
Note: Technically, you can use pip or pipx to install ty as well.
Running ty Locally
Running with uv
uv run ty
Running without Installation
uvx ty
Running ty Directly
ty check
Configuring ty
- pyproject.toml
- ty.toml
There are many rules that you can change. Check out the documentation for full details.
In general, if you run mypy in strict mode, then running ty without changing any of its settings is very similar. However, ty currently does not highlight missing type hints. If you need to enforce adding type hints, you can use Ruff’s flake8-annotations.
Here is how to enable the flak8-annotations in your pyproject.toml file:

If you have other rules already selected, you can add “ANN” to the end of the list to enable it.
Running ty in GitHub Actions
.github/workflows/ty.yml
Make sure you include the leading period!
Next, inside your yaml file, you will add the following code:
name: ty
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:
jobs:
build:
if: github.event.pull_request.draft == false
runs-on: self-hosted
steps:
- uses: actions/checkout@v3
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: “3.12”
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ty==0.0.7
- name: Run ty
run: ty check
continue-on-error: false
Now, when your team opens a new PR in your project, it will automatically run ty against it. Feel free to update the Python version to the one you are using. Also note that this GitHub Action sets the ty version to 0.0.7, which you may need to update as newer releases become available.
Using ty with pre-commit
- https://github.com/NSPC911/ty-pre-commit
- https://github.com/JacobCoffee/ty-pre-commit
- https://github.com/hoxbro/ty-pre-commit
- https://github.com/allganize/ty-pre-commit
- https://foundry.fsky.io/vel/ty-pre-commit
When Astral supports pre-commit itself, you should update your pre-commit configuration accordingly.
However, for this tutorial, you can use that first link which tells you to add the following to your .pre-commit-config.yaml:

Now, when you commit a file locally, pre-commit will run ty to check it for you automatically.
Wrapping Up
Type checkers can be really helpful in finding subtle bugs in your Python code. However, remembering to run them before pushing your code can be difficult, so make your life easier by adding the type checker to your CI!
Have fun and happy coding!
The post How to Switch to ty from Mypy appeared first on Mouse Vs Python.
The Python Coding Stack
Parkruns, Python’s enumerate and zip, and Why Python Loops Are Different from Other Languages • [Club]
If you live in the UK, you’re probably familiar with the Parkrun tradition: a friendly 5k run held every Saturday morning in hundreds of parks across the UK. Runners range from Olympians to people trying to lose some weight. It’s a well-oiled format replicated across all 893 Parkrun locations.
And here’s how they deal with the finish line logistics. Runners don’t wear bibs with numbers. When they cross the finish line, they enter a “funnel” marked by plastic cones and are handed a token with their position number. They then proceed to another official, who scans their personal barcode, which runners carry in their pockets or on a wristband, and the position token they received a few seconds earlier. This process matches the runner with their finishing position.
What’s this got to do with Python loops? And how does it help us understand why Python does loops differently from other languages?
First step, let’s create the Parkrun funnel. I’ll just put the first five finishers in this example:
>>> funnel = [”Jonathan”, “Michael”, “Samantha”, “Jessica”, “Daniel”]Now, here’s something you definitely know already because it’s always one of the first things you’re taught when learning Python: Don’t loop through this list like this:
# Avoid this when coding in Python
>>> i = 0
>>> while i < len(funnel):
... name = funnel[i]
... print(name)
... i += 1
...
Jonathan
Michael
Samantha
Jessica
DanielThis style mimics how other languages may work: you manually define and increment the index. To be fair, most people who shift from other languages are more likely to write the following version at some point:
# Also best to avoid this in Python
>>> for i in range(len(funnel)):
... name = funnel[i]
... print(name)
...
Jonathan
Michael
Samantha
Jessica
DanielThis version may seem more Pythonic since it uses Python tools such as range(), but still fails to make the most of Python’s iteration protocol. The Pythonic way of looping through this list is the following:
>>> for name in funnel:
... print(name)
...
Jonathan
Michael
Samantha
Jessica
DanielA question that’s often asked but rarely answered is: Why is this version preferred over the other two? I’ll write another short post to answer this question soon as I want to keep these The Club posts short whenever possible. So, let me state just a few reasons (there are more) and then I’ll move on to my main topic for today.
It’s more readable
It’s more efficient (try timing the versions above using
timeit—remove theprint()calls first)It’s less prone to errors and bugs
It works with a broader selection of data structures, not just sequences
While you wait for my follow-up post on this, you can read more about Python’s Iterator Protocol, iterables, and iterators here:
But let’s move on.
Let’s say you want to print out the names alongside each runner’s position. You’d like the following output:
1. Jonathan
2. Michael
3. Samantha
4. Jessica
5. Daniel“Aha!” I’m often told by some learners, “This is when you need to use the for i in range(len(funnel)) idiom, since you need the index!”
Python’s for loop doesn’t explicitly use the index, so you don’t have access to the index within the for loop. Many revert to the non-Pythonic idioms for this.
But Python provides tools that let you stay within the pure Pythonic style. Python’s for loop needs an iterator—it will create one from the iterable you provide. All Python iteration needs iterators, not just for loops. Iterators are Python’s tool for any iteration.
And there are some bespoke iterators in Python that handle most of your iteration needs. I recently wrote a series about the itertools module. The itertools module contains many such tools. Here’s the series: The itertools Series.
But there are also two built-in tools that many forget, but are extremely useful. The first one is enumerate().
Here’s how you can use it to display the Parkrun results:
>>> for index, name in enumerate(funnel, start=1):
... print(f”{index}. {name}”)
...
1. Jonathan
2. Michael
3. Samantha
4. Jessica
5. Daniel
Real Python
The Real Python Podcast – Episode #279: Coding Python With Confidence: Beginners Live Course Participants
Are you looking for that solid foundation to begin your Python journey? Would the accountability of scheduled group classes help you get through the basics and start building something? This week, two members of the Python for Beginners live course discuss their experiences.
[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]
January 08, 2026
Rodrigo Girão Serrão
Recursive structural pattern matching
Learn how to use structural pattern matching (the match statement) to work recursively through tree-like structures.
In this short article you will learn to use structural pattern matching in recursive, tree-like data structures.
The examples from this article are taken from a couple of recent issues of my weekly newsletter.
A recursive data structure
Structural pattern matching excels at... matching the structure of your objects! For the two examples in this article, we'll be using a number of dataclasses that you can use to build abstract Boolean expressions:
from dataclasses import dataclass
class Expr:
pass
@dataclass
class And(Expr):
exprs: list[Expr]
@dataclass
class Or(Expr):
exprs: list[Expr]
@dataclass
class Not(Expr):
expr: Expr
@dataclass
class Var(Expr):
name: str
For example, the code Not(And([Var("A"), Var("B")])) represents the Boolean expression not (A and B).
Evaluating a Boolean expression
Suppose you have a Boolean expression built out of the components shared above. How do you evaluate that formula if you are given the assignments that map the variables to their values?
For example, if you have the assignments {"A": True, "B": False} (for example, a dictionary that maps variable names to values), how can you determine that the expression Not(And([Var("A"), Var("B")])) is True?
This is where structural pattern matching can be applied recursively and it's where it really shines!
To solve this problem, you will write a function called evaluate(expression: Expr, assignments: dict[str, bool]) -> bool.
Your function accepts an expression and the assignments in the form of a dictionary and it returns the final Boolean value the expression evaluates to.
Since you're accepting an expression, you're going to use the match statement on the full expression and then create a case branch for each of the possible expressions you might have:
- a variable;
- an
Andexpression; - an
Orexpression; or - a
Notexpression.
The structure of the code looks like this:
def evaluate(expression: Expr, assignments: dict[str, bool]) -> bool:
match expression:
case Var(): pass
case And(): pass
case Or(): pass
case Not(): pass
The trick here is realising that you're using Expr as the type of the argument but really, you always expect the argument to be an instance of one of the subclasses of Expr, and not a direct Expr instance.
However, to make sure you don't trip on a weird bug later on, and because this matching is supposed to be exhaustive – you're supposed to have one case for each subclass of Expr – you can defend yourself by including a catch-all pattern that raises an error.
When I'm being lazy, I just raise a RuntimeError:
def evaluate(expression: Expr, assignments: dict[str, bool]) -> bool:
match expression:
case Var(): pass
case And(): pass
case Or(): pass
case Not(): pass
case _:
raise RuntimeError(
f"Couldn't evaluate expression of type {type(expression)}."
)
Now, it's just a matter of implementing the evaluation logic. In the case of a variable, all you have to do is fetch the variable value from the corresponding dictionary. However, to make it more convenient to...
Stéphane Wirtel
Automating TLS Certificate Monitoring with GitHub Actions, certificate_watcher, and Slack
Introduction
As a consultant constantly working with clients, I found myself in a familiar predicament: my head was always down, focused on delivering value to customers, but my own infrastructure monitoring was non-existent. I had no simple way to track SSL/TLS certificate expirations across the multiple domains I managed - personal sites, client projects, and community services.
I needed a solution, but I had several constraints:
- No time for complex setup: I couldn’t afford to spend days installing, configuring, and deploying yet another monitoring service
- Easy maintenance: Whatever I built had to be low-maintenance - I didn’t want another system to babysit
- Transparency and control: I wanted a simple text file in Git listing the hosts to monitor, so I could see exactly what was being checked and track changes over time
- Zero infrastructure: No servers to provision, patch, or pay for
Around this time, a friend named Julien shared his project called certificate_watcher, a lightweight Python tool for checking SSL certificate expiration. I contributed a few patches (if memory serves), and it clicked: what if I could combine this with GitHub Actions and Slack notifications?
January 07, 2026
Real Python
How to Build a Personal Python Learning Roadmap
If you want to learn Python or improve your skills, a detailed plan can help you gauge your current status and navigate toward a target goal. This tutorial will help you craft a personal Python learning roadmap so you can track your progress and stay accountable to your goals and timeline:
The steps in this tutorial are useful for Python developers and learners of all experience levels. While you may be eager to start learning, you might want to set aside an hour or two to outline a plan, especially if you already know your learning goals. If you don’t yet have clear goals, consider spreading that reflection over a few shorter sessions across several days to clarify your direction.
Before you start, gather a few practical tools to support building your plan. This might include a notebook, a calendar or planner (digital or physical), a list of projects or goals you want to work toward, and any Python books or online resources you plan to use.
Note: If you learn best with structure and accountability, you can also follow your roadmap inside a cohort-based live course delivered by Real Python experts, with weekly live classes and live Q&A.
You can download a Personal Python Learning Roadmap worksheet to help you create your plan by clicking the link below:
Get Your Python Learning Roadmap: Click here to download a free, fillable Python learning roadmap PDF to help you set your aims and track your progress.
This tutorial will guide you through the planning process, starting with clarifying what you want to achieve and why. From there, you’ll map out the practical steps that will turn your goals into a realistic, actionable roadmap.
Step 1: Define Your Goals and Motivation
To create an effective learning roadmap, you first need to know what you want to achieve and what your motivation is. For this step, you’ll consider the following reflection prompt:
What do I want to accomplish with Python, and why?
Taking the time to answer this question sets the foundation for every decision you’ll make as you build your roadmap.
Define Your Goals
Start by deciding what you want to accomplish with Python, then write it down. Research shows that this small step can make a meaningful difference. In a study conducted by psychology researcher Dr. Gail Matthews at Dominican University of California, participants who wrote down their goals were significantly more likely to achieve them than those who didn’t.
If you’re not sure yet about your goals, here are some questions for you to consider:
-
Are there specific projects—or types of projects—that you’d like to work on? For example, data analysis, game development, or building a web app.
-
In what context or setting would you like to use your Python skills? For example, at work, in school, or as part of a personal interest or side project.
Remember to write these answers down either in your notebook or on the Personal Python Learning Roadmap worksheet included in this tutorial’s downloads. Having them written down will provide helpful context as you continue formulating your roadmap.
Determine Your Motivation
Once you have a general goal in mind, think about why you want to achieve it. Your motivation plays a key role in whether you’ll stick with your plan over time. As clinical psychology professor Dr. Jennifer Crawford explains:
If we don’t care about why we’re doing [a goal], then it makes it really difficult to stick with that new behavior.
— Dr. Jennifer Crawford
She also encourages goal-setters to ask how their goals connect with something that’s important to them.
This idea is echoed by psychology professor Angela Duckworth in her book Grit, where she emphasizes that a strong sense of purpose helps you persevere when you encounter obstacles that might otherwise derail your progress.
Some possible reasons behind your “why” might include:
- A personal interest or a love of learning
- A desire to start or advance a career in software development
- A goal of earning a computer science degree
- An interest in volunteering your skills—for example, creating a Python application that supports a cause you care about
As you consider your motivation, see if you can dive deeper into the root of your reasons. A deeper look can add even more meaning and staying power to your goals. For example:
Read the full article at https://realpython.com/build-python-learning-roadmap/ »
[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]
Stéphane Wirtel
dsmtpd 1.2.0: Test Your Emails Risk-Free
The Test Email That Never Should Have Been Sent
You know that feeling? You’re developing a new email feature, you run your test script, and boom — you realize 3 seconds too late that you used the production database. Your CEO just received an email with the subject “TEST - DO NOT READ - LOREM IPSUM”.
Or worse: you configured a cloud SMTP server for testing, forgot to disable actual sending, and now your Mailgun account is suspended for “suspicious activity” because you sent 847 emails to test@example.com in 5 minutes.
Python⇒Speed
Unit testing your code's performance, part 1: Big-O scaling
When you implement an algorithm, you also implement tests to make sure the outputs are correct. This can help you:
- Ensure your code is correct.
- Catch problems if and when you change it in the future.
If you’re trying to make sure your software is fast, or at least doesn’t get slower, automated tests for performance would also be useful. But where should you start?
My suggestion: start by testing big-O scaling. It’s a critical aspect of your software’s speed, and it doesn’t require a complex benchmarking setup. In this article I’ll cover:
- A reminder of what big-O scaling means for algorithms.
- Why this is such a critical performance property.
- Identifying your algorithm’s scalability, including empirically with the
bigOlibrary. - Using the
bigOlibrary to test your Python code’s big-O scalability.
January 06, 2026
PyCoder’s Weekly
Issue #716: Performance Numbers, async Web Apps, uv Speed, and More (Jan. 6, 2026)
#716 – JANUARY 6, 2026
View in Browser »
PyCoder’s Weekly 2025 Top Articles & Hidden Gems
PyCoder’s Weekly included over 1,500 links to articles, blog posts, tutorials, and projects in 2025. Christopher Trudeau is back on the show this week to help wrap up everything by sharing some highlights and uncovering a few hidden gems from the pile.
REAL PYTHON podcast
Python Numbers Every Programmer Should Know
Ever wonder how much memory an empty list takes? How about how long it takes to add two integers in Python? This post contains loads of performance data for common Python operations.
MICHAEL KENNEDY
Webinar: Building Deep Agents with Scale AI & Temporal
Build AI agents that don’t stop running. Join Scale AI and Temporal to learn how Agentex and Python enables long-running, fault-tolerant agents with human-in-the-loop workflows, plus a live procurement agent demo →
TEMPORAL sponsor
What async Really Means for Your Python Web App?
Python continues to get better async support and with that comes pressure to switch. See the realistic effects that switching to async would have on your web servers.
ARTEM CHERNYAK
How uv Got So Fast
uv’s speed comes from engineering decisions, not just Rust. Static metadata, dropping legacy formats, and standards that didn’t exist five years ago.
ANDREW NESBITT
Articles & Tutorials
Python 3.6-3.14 Performance
One of the maintainers of Knave has been tracking Python performance data for a while and a recent upgrade of one of their machines meant they now had more info across different hardware. This post compares their performance test across Apple M1 & M5, Zen2 and Cascade Lake chips.
CREWTECH
Static Protocols in Python: Behaviour Over Inheritance
Static protocols bring structural typing to Python: type compatibility based on behaviour, not inheritance. This article explains how protocols differ from ABCs, goose typing, and classic duck typing, and how static type checkers use them to catch errors early.
PATRICKM.DE • Shared by Patrick Müller
Get Job-Ready With Live Python Training
Real Python’s 2026 cohorts are open. Python for Beginners teaches fundamentals the way professional developers actually use them. Intermediate Python Deep Dive goes deeper into decorators, clean OOP, and Python’s object model. Live instruction, real projects, expert feedback. Learn more at realpython.com/live →
REAL PYTHON sponsor
How to Build Internal Developer Tools With a Small Team
This opinion piece talks about how to build internal dev tools. It provides a mental model of product engineering to help decide whether to prioritise improving stability or adding new features.
PATRICKM.DE • Shared by Patrick Müller
How to Securely Store Secrets in Environment Variables
You shouldn’t store API keys, tokens, or other secrets with your code, they need to be protected separately. In this post, Miguel discusses how he handles secrets with environment variables.
MIGUEL GRINBERG
2025 Python Year in Review
Talk Python interviews Barry Warsaw, Brett Cannon, Gregory Kapfhammer, Jodie Burchell, Reuven Lerner, and Thomas Wouters and the panel discusses what mattered for Python in 2025.
TALK PYTHON podcast
Python Supply Chain Security Made Easy
Learn how to integrate Python’s official package scanning technology into your processes to help ensure the security of your development environment.
MICHAEL KENNEDY
PyPI in 2025: A Year in Review
Dustin summarizes all the happenings with the Python Packaging Index in 2025, including 130,000 new projects and over 2.5 trillion requests served.
DUSTIN INGRAM
Top Python Libraries of 2025
Explore Tryo-labs’ 11th annual Top Python Libraries roundup, featuring two curated Top 10 lists: one for General Use and one for AI/ML/Data tools.
DESCOINS & BELLO
Implicit String Concatenation
Python automatically concatenates adjacent string literals thanks to implicit string concatenation. This feature can sometimes lead to bugs.
TREY HUNNER
Safe Django Migrations Without Server Errors
How to run schema-changing Django migrations safely, avoiding schema/code mismatches and server errors during rolling deployments.
LOOPWERK
Projects & Code
vresto: Interface for Copernicus Sentinel Data
An elegant Python interface for discovering and retrieving Copernicus Sentinel data.
GITHUB.COM/KALFASYAN • Shared by Yannis Kalfas
Liberty Mail: Email Client for Sales Outreach
GITHUB.COM/EYEOFLIBERTY • Shared by [Ivan Kuzmin]
Events
Weekly Real Python Office Hours Q&A (Virtual)
January 7, 2026
REALPYTHON.COM
Python Atlanta
January 9, 2026
MEETUP.COM
PyDelhi User Group Meetup
January 10, 2026
MEETUP.COM
DFW Pythoneers 2nd Saturday Teaching Meeting
January 10, 2026
MEETUP.COM
PiterPy Meetup
January 13, 2026
PITERPY.COM
Leipzig Python User Group Meeting
January 13, 2026
MEETUP.COM
Happy Pythoning!
This was PyCoder’s Weekly Issue #716.
View in Browser »
[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]
Django Weblog
Django bugfix releases issued: 5.2.10, 6.0.1
Today we've issued the 5.2.10 and 6.0.1 bugfix releases.
The release packages and checksums are available from our downloads page, as well as from the Python Package Index.
The PGP key ID used for these releases is Jacob Walls: 131403F4D16D8DC7
Real Python
Tips for Using the AI Coding Editor Cursor
Cursor is an AI-powered integrated development environment (IDE) based on the Visual Studio Code codebase. It comes with a multi-agent interface and the Composer model for fast, agentic coding while keeping a familiar editor workflow with project-aware chat, code completion, and inline edits.
In this course, you will:
- Understand why Cursor might work for you
- Learn how to use different modes & models
- See how to run multiple agents at a time
- Resolve a tiny merge conflict
- Run a project and fix a bug
- Learn commands and practice with the terminal
[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]
Seth Michael Larson
“Food JPEGs” in Super Smash Bros & Kirby Air Riders
Have you ever noticed that the food graphics in Super Smash Bros. and Kirby Air Riders are flat “billboarded” stock images of food?
This artistic decision from director Masahiro Sakurai has persisted through 8 games over nearly 25 years. I've seen a few folks online remarking about the “JPEG” or “PNG”-like quality of the images in the most recent release: Kirby Air Riders.
While researching every game with this art style and all 150+ unique food images I ended up fixing wikis, reviewing a seasonal KitKat flavor, and preserving an uncatalogued image of tempura soba.
Burgers from Super Smash Bros. Melee (2001), Kirby Air Ride (2003), Super Smash Bros. Brawl (2008), Super Smash Bros. Ultimate (2018), and Kirby Air Riders (2025).
Masahiro Sakurai is the director for every game on this list, so clearly this is his artistic decision. Super Smash Bros. Melee was the first game to contain this food art style, published in 2001. This style was then repeated in Kirby Air Ride (2003), Super Smash Bros. Brawl (2008), Super Smash Bros. for 3DS and Wii U (2014), Super Smash Bros. Ultimate (2018), and most recently in Kirby Air Riders (2025).
Credit to Nintendo, HAL Laboratories, SORA Ltd., and Bandai Namco Studios as developers and publishers of these games. Artwork was sourced from the Spriters Resource.
Super Smash Bros. Melee (2001)
Where it all began! Super Smash Bros. Melee for the GameCube started off with 28 distinct food items, often found in “Party Balls”. Each type of food had a different “nutritional value” and “yumminess quotient” according to the in-game trophy dedicated to the food items.
Melee included many foods specific to Japanese cuisine, such as unagi (eel), omurice, soba, dango, and gyūdon. I do distinctly remember growing up as a “culinarily sheltered” kid in the midwest United States and not understanding what many of these food items were.
The original stock images of Super Smash Bros. Melee and the next game, Kirby Air Ride, have been partially discovered and documented by a group called “Render96”. The stock images are from a company called “Sozaijiten”. Many of the food images come from Material Dictionary CDs Volume 14 (Vegetables & Fruits), Volume 22 (Food & Dishes), and Volume 73 (Cooking Japanese, Western, & Chinese). The apple stock image in particular was re-used all the way through Super Smash Bros. Ultimate (2018). The burger, milk, dango, and donut are still missing their primary source.
Kirby Air Ride (2003)
Kirby Air Ride for the GameCube had significantly fewer distinct food items (12) compared to Melee and maintained many of the same food stock images from Melee, including the apple, burger, chicken, curry, omurice, onigiri, and ramen. Nigiri was included, but the image was changed from a sushi board to a plate.
The stock images had their saturation increased and the black borders around the images are thicker, sometimes 2-3 pixels instead of only 1 pixel for Melee.
I paid $50 plus shipping on eBay for this PNG. This is the closest I'll get to NFTs.
While researching the foods in Kirby Air Ride I discovered a wiki description of a “tempura soba” item that I'd never heard of and wasn't included in the Spriters Resource spritesheets for Kirby Air Ride. Turns out that this item was changed to a “hotdog” in the NSTC-M and PAL releases of Kirby Air Ride.
I was unable to find a non-blurry image of the tempura soba sprite online, so of course I had to preserve this sprite myself. I purchased a Japanese copy of Kirby Air Ride, dumped the ROM using the FlippyDrive Disc Backup Utility, and ran the ROM using Dolphin with “Dump Textures” mode enabled to archive the sprite directly from the game.
Kirby Air Ride cover artwork (left: JP, right: US, PAL). Images from the GameTDB.
In the process I also learned that the cover of Kirby Air Ride changed between the Japanese and international releases. The Japanese cover art features a smiling happy Kirby where the international cover has Kirby with a furrowed brow and serious look.
Super Smash Bros. Brawl (2008)
Super Smash Bros. Brawl for the Wii has only one more food item compared to Melee (29) and introduces 11 new food items including bread, cake, candy, chocolate, cookie, melon soda, parfait, peaches, pie, pineapple, and steak.
About half of the Japanese-specific foods from both Melee and Kirby Air Ride were replaced: curry, omurice, onigiri, and ramen.
The art is less saturated and more “realistic” which is in-line with the rest of the game's art direction. The images lost their black outline, likely to draw less attention to the “arcade-y” feel that the previous titles had with food items.
Super Smash Bros 3DS and Wii U (2014)
Super Smash Bros. Wii U and 3DS have the same total number of food items as Brawl (29). These games change the food art style completely, again! It's brighter, saturated, and looks delicious.
The soda item was changed from a melon cream soda to a dark cola with lemon. The omurice was changed to a pair of fried eggs with bacon. These games are also the only ones without the “burger” food item.
Super Smash Bros. for 3DS uses the same food artwork used in Super Smash Bros. for Wii U downscaled to 64x64 pixels from 256x256 pixels with some minor editing.
Super Smash Bros. Wii U and 3DS added the “Mont Blanc” food item, which is a French dessert that is popular in Japan. I've seen multiple guides and wikis mistakenly label this food item as “noodles” due to the “vermicelli” shape of the puréed chestnuts. Yummy!
While researching and writing this blog post I happened across “Mont Blanc”-flavored KitKats. These are apparently a limited-time flavor for autumn. The KitKats are creamy and have plenty of chestnut flavor, but they are very sweet (apparently Mont Blanc is quite sweet, too, so this is to be expected).
Mont Blanc food item from Super Smash Bros Wii U, 3DS, and Ultimate
“Mont Blanc flavored limited-time KitKats”
Super Smash Bros. Ultimate (2018)
Super Smash Bros. Ultimate uses the same 29 foods from the Wii U and 3DS and adds 9 more foods for a total of 38. Many of the newly added foods are call-backs to food items in previous titles, below highlighted in pink.
The 9 new foods in Ultimate are burgers, cheese, corndogs, donuts, dumplings, daisies, pizza, pineapple, and steak.
It's clear that the “Sozaijiten” stock images were still in use even in 2018: 17 years later! The apple, cheese, and chicken stock images for Super Smash Bros. Melee match the stock images used in Ultimate.
Kirby Air Riders (2025)
Kirby Air Riders released for the Switch 2 has the most foods of any game with this art style with 45 distinct food items.
Massive thank-you to Charles Bernardo for sending me carefully cropped images of the food in Kirby Air Riders.
Kirby Air Riders is the first game in this series to use completely new models for all food items: not even the apple or cheese are the same from any previous game. Kirby Air Riders is also the first game in this series not to have a “roast chicken” item, breaking from an established video-game food trope.
Kirby Air Riders adds a new food-centric mode called “Gourmet Race” where riders earn points by consuming food as quickly as possible in a small arena. Gourmet Race introduces a new food concept: “Large Foods”. Large food items are worth 15 points instead of 1 point per food item. There are 15 large food items, some presenting as “upgraded” versions of regular-sized foods.
The large food items are: a bunch of 12 bananas instead of 3, a bread-basket, a double cheeseburger, a whole cake instead of a slice, donuts, a fruit basket, a board of nigiri instead of a plate, fruit parfait, pizza, popcorn, salad, rainbow shave ice instead of blue only, a prime rib steak, a tempura bowl, and a whole watermelon instead of a slice.
Prior to this article there was not yet a complete list of foods in Kirby Air Riders documented on a wiki or spritesheet. I added this list to the Kirby wiki, but I've also included the list below:
List of food items in Kirby Air Riders
- Apple
- Bananas
- Bread Basket
- Cake (Slice)
- Cake (Whole)
- Cheese
- Cheeseburger
- Cheeseburger (Double)
- Chocolate
- Cola
- Cupcake
- Donuts
- Dumpling
- Omurice
- French Fries
- Fried Rice
- Fruit Basket
- Gelatin
- Grapes
- Hamburg Steak
- Hotdog
- Icecream
- Jelly Beans
- Lettuce
- Melon Cream Soda
- Nigiri (Plate)
- Nigiri (Board)
- Orange
- Orange Juice
- Pancakes
- Parfait
- Spaghetti
- Pizza
- Popcorn
- Ramen
- Salad
- Sambusas
- Sandwich
- Shave Ice (Blue)
- Shave Ice (Rainbow)
- Prime Rib Steak
- Tempura Bowl
- Watermelon (Slice)
- Watermelon (Whole)
Unique food items
There are 16 total food items that only appear in a single title across the 25-year span of games. Kirby Air Riders and Super Smash Bros. Melee have by far the most unique food items with 8 and 5 respectively.
| Game | Count | Foods |
|---|---|---|
| Super Smash Bros. Melee | 5 | Dango, Gyūdon, Mushroom, Soba, Unagi |
| Kirby Air Ride | 0 | |
| Super Smash Bros. Brawl | 1 | Cookie |
| Super Smash Bros. Wii U/3DS | 0 | |
| Super Smash Bros. Ultimate | 2 | Daisy, Corndog |
| Kirby Air Riders | 8 | Cupcake, French Fries, Fruit Basket, Gelatin, Jelly Beans, Lettuce, Sambusas, Sandwich |
Comparing food across games
Finally, here is a table with every image so you can compare how each changed across different titles:
| SSB Melee 2001 |
Kirby Air Ride 2003 |
SSB Brawl 2008 |
SSB Wii U/3DS 2014 |
SSB Ultimate 2018 |
Kirby Air Riders 2025 |
|
|---|---|---|---|---|---|---|
| Apple | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
| Bananas | ![]() | ![]() ![]() | ||||
| Bread | ![]() | ![]() | ![]() | ![]() | ||
| Burger | ![]() | ![]() | ![]() | ![]() | ![]() ![]() | |
| Cake | ![]() | ![]() | ![]() | ![]() ![]() | ||
| Candy | ![]() | ![]() | ![]() | |||
| Cheese | ![]() | ![]() | ![]() | ![]() | ||
| Cherries | ![]() | ![]() | ||||
| Chicken | ![]() | ![]() | ![]() | ![]() | ![]() | |
| Chocolate | ![]() | ![]() | ![]() | ![]() | ||
| Cola | ![]() | ![]() | ![]() | |||
| Cookie | ![]() | |||||
| Corndog | ![]() | |||||
| Cupcake | ![]() | |||||
| Curry | ![]() | ![]() | ||||
| Daisy | ![]() | |||||
| Dango | ![]() | |||||
| Donut | ![]() | ![]() | ![]() | ![]() | ||
| Dumplings | ![]() | ![]() | ||||
| Eggs | ![]() | ![]() | ![]() | ![]() | ![]() | |
| Fried Rice | ![]() | ![]() | ![]() | |||
| Fries | ![]() | |||||
| Fruit Basket | ![]() | |||||
| Gelatin | ![]() | |||||
| Grapes | ![]() | ![]() | ![]() | ![]() | ![]() | |
| Gyūdon | ![]() | |||||
| Hamburg Steak | ![]() | ![]() | ![]() | |||
| Hotdog | ![]() | ![]() | ![]() | ![]() | ![]() | |
| Icecream | ![]() | ![]() | ![]() | ![]() | ||
| Jellybeans | ![]() | |||||
| Kebab | ![]() | ![]() | ||||
| Kiwi | ![]() | ![]() | ||||
| Lemons | ![]() | ![]() | ||||
| Lettuce | ![]() | |||||
| Melon | ![]() | ![]() | ||||
| Melon Soda | ![]() | ![]() | ||||
| Milk | ![]() | ![]() | ![]() | ![]() | ||
| Mont Blanc | ![]() | ![]() | ||||
| Mushroom | ![]() | |||||
| Nigiri | ![]() | ![]() | ![]() | ![]() ![]() | ||
| Onigiri | ![]() | ![]() | ![]() | ![]() | ||
| Orange | ![]() | ![]() | ||||
| Orange Juice | ![]() | ![]() | ![]() | |||
| Pancakes | ![]() | ![]() | ![]() | |||
| Parfait | ![]() | ![]() | ||||
| Pasta | ![]() | ![]() | ![]() | ![]() | ||
| Peach | ![]() | ![]() | ![]() | |||
| Pear | ![]() | ![]() | ||||
| Pie | ![]() | ![]() | ![]() | |||
| Pineapple | ![]() | ![]() | ||||
| Pizza | ![]() | ![]() | ![]() | ![]() | ||
| Popcorn | ![]() | ![]() | ![]() | |||
| Ramen | ![]() | ![]() | ![]() | |||
| Salad | ![]() | ![]() | ![]() | |||
| Sambusas | ![]() | |||||
| Sandwich | ![]() | |||||
| Shave Ice | ![]() ![]() | |||||
| Shumai | ![]() | ![]() | ![]() | |||
| Soba | ![]() | |||||
| Soup | ![]() | ![]() | ||||
| Steak | ![]() | ![]() | ![]() | |||
| Strawberry | ![]() | ![]() | ||||
| Tea | ![]() | ![]() | ![]() | ![]() | ||
| Tempura | ![]() | ![]() | ![]() | ![]() | ||
| Unagi | ![]() | |||||
| Watermelon | ![]() | ![]() | ![]() ![]() |
Thanks for keeping RSS alive! ♥
January 05, 2026
PyCharm
The next edit suggestions feature is now enabled in all JetBrains IDEs for JetBrains AI Pro, AI Ultimate, and AI Enterprise subscribers.
Yes, you read that right! JetBrains-native diff suggestions are available right in your editor. Global support for optimized latency. Out-of-the-box IDE actions for reliability. And the best part? It doesn’t consume your AI quota.
What are next edit suggestions?
Like the suggestions provided by AI code completion, next edit suggestions (NES) appear as you type. The difference is that NES can be proposed beyond the immediate vicinity of your caret, and they can modify existing code instead of exclusively adding new code. This feature is a natural extension of code completion, and together they comprise the in-flow Tab-Tab experience.
The NES feature runs silently in the background, generating suggestions as you modify your code. It then gives you the option to review and decide whether to accept them in a small in-editor diff view (the NES UI). The feature adapts how it presents the suggestions, showing them to you in the least intrusive way to avoid interfering with your work. Large changes appear in a dedicated diff view, while smaller suggestions are shown in a larger popup.
Overall, NES provide a smart code editing experience. Let’s agree to share responsibilities as follows: you can simply type and continue development as you used to, and we suggest small digestible diffs that help you do your job faster. Deal?
Who can use NES?
With the latest AI Assistant update, next edit suggestions are enabled by default for all users with AI Pro, AI Ultimate, or AI Enterprise subscriptions. Unlike AI code completion, the next edit suggestions feature is currently unavailable for AI Free license holders. Stay tuned, though – we are actively working on bringing it to a wider audience!
You can always learn more about which AI features are available in different pricing tiers on our official page.
How do NES work?
Trust us, there is a lot we could say about the internals, but we’ll try to keep things simple here.
Long story short, next edit suggestions are where AI meets 🤝 the intelligence of JetBrains IDEs. Under the hood, the feature calls our cloud-based custom AI model and leverages deterministic IDE actions where possible.
AI model
Currently, at their core, NES rely mostly on suggestions provided by a model fine-tuned specifically for this task.
Much like Mellum, the model is a small language model (SLM) that leverages cloud GPU infrastructure to provide the best possible latency all around the world. Unlike Mellum, however, the underlying model is bigger and leverages a different type of context: the history of your recent changes as opposed to the current file and RAG.
Bigger does not always mean slower! Our inference pipelines differ for code completion and next edit suggestions generation. NES employ several inference tricks that keep latency under 200 ms for the majority of requests, even at the busiest times of the day 💪. If you ever thought that completion in JetBrains IDEs was slow, it’s time to reconsider!
IDE actions (code insights)
Developers love our IDEs because of their reliability, and next edit suggestions put that aspect at your fingertips.
As part of their pipeline, when invoked, NES look for available code insights provided by the IDE and show them in the NES UI if they are appropriate. One of the easiest ways to see this interaction at work would be to look at a suggestion that renames an identifier in a file. The next edit suggestion will activate the IDE’s Rename refactoring, and usages will be conveniently updated. This even works with multi-file search!
The integration between next edit suggestions and IDE code insights is not yet fully complete. Because even frontier models struggle with out-of-distribution tools, or even just having a large number of tools in general, we are intentionally adding new IDE actions to NES slowly. We are prioritizing the ones that are useful the most often, as well as the ones the models can use most effectively. Let us know in the comments which IDE actions you would find useful in NES!
Summary
Next edit suggestions don’t replace the existing forms of code completion, but complement them, ensuring the best speed and relevance. Where code completion provides suggestions for new material, the next edit suggetions model works in the field of, well, edits. It is optimised to propose changes to existing code, but sometimes the best edit is simply to add something new. In those cases, the suggestions will look like completions because they are presented the same way – as inline gray text.
The simple scheme below explains which suggestion provider can be handled by which UI.
Settings panel update
In addition to enabling this new feature, we are redesigning the settings for AI code completion and next edit suggestions. Shortly after the start of the new year, the settings for these features will be simplified. Instead of having to navigate multiple views, you will be able to view everything on a single screen, with all the most important options available.
Here’s a sneak peek of the new design:
As you can see, the settings for local completion, cloud-based completion, and next edit suggestions are all combined on a single page where you can decide what you want and what you don’t.
AI code completion and NES cheat sheet
Deciding which types of suggestions to enable may feel a bit overwhelming, so we’ve put together a short cheat sheet to help clarify which settings to enable in the new settings panel, depending on your preferred workflow.
Case 1: You don’t want AI in your editor
Simply turn off inline completion and next edit suggestions on this panel. We’ll make sure you don’t see any results of matrix multiplications.
Case 2: You don’t want cloud-based suggestions
Just turn on inline completion with local models. Those models are already bundled into your IDE and work without an internet connection. Good ol’ full line code completion will have your back.
If you want your own local solution, you can plug any open-source model into the IDE via LM Studio or Ollama. This option is available on the AI Assistant | Models settings page. Note that, currently, this option only works for code completion. We will closely monitor the level of quality that is possible with local inference for NES, with the aim of eventually including it as well.
Case 3: You like completion but NES seem off
In this case, the best solution is to turn on inline completion with the Cloud and local models option and make sure that next edit suggestions are turned off. You will get the best from the Mellum model, and the IDE will automatically fall back to local models if your internet connection is unstable.
Case 4: You like full-blown in-editor AI assistance
Turn on both cloud models for inline completion and next edit suggestions to get code snippet suggestions as you modify your source code.
What’s next for NES?
Here is a quick look at some of the improvements we’re already working on:
- Smarter and more precise suggestions
- More IDE actions for NES to use
- Longer tab sequences
Many other developments are on our radar, and we’ll keep you updated as they come closer to fruition.
Thank-you note
While you update your AI Assistant and GPUs go brrr, we would like to thank everyone who participated in the open Beta test for the next edit suggestions feature this fall.
Over the last few months, the feature has been available to JetBrains AI subscribers who were willing to try it and share anonymous usage statistics. With your help, we were able to make sure the feature was ready and properly prepare the cloud infrastructure for a full-scale release. Thank you so much! ❤️
Tab–Tab,
Your AI completion team
Real Python
Learn From 2025's Most Popular Python Tutorials and Courses
As we welcome 2026, it’s time to look back on an exciting year for Python. Python 3.14 arrived with a wave of developer-focused improvements, from lazy annotations that finally resolve long-standing type hinting quirks to clever new t-strings that give you more control over string interpolation.
Meanwhile, 2025 was the year AI tools truly became part of everyday Python development. From agentic coding with LangGraph to connecting LLMs to your data via MCP servers, Python solidified its position as the language of choice for AI-powered workflows.
Here at Real Python, we’re excited to showcase the tutorials and video courses that resonated most with our community throughout 2025. Whether you’re just starting out with Python, leveling up your skills, or diving into advanced topics, this collection has something for you.
Join Now: Click here to join the Real Python Newsletter and you’ll never miss another Python tutorial, course, or news update.
Take the Quiz: Test your knowledge with our interactive “Python Skill Test” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python Skill TestTest your Python knowledge in a skills quiz with basic to advanced questions. Are you a Novice, Intermediate, Proficient, or Expert?
Strengthen Your Python Foundations
If you’re just getting started with Python, building a solid foundation is the best investment you can make. The fundamentals you learn now will support everything you do later, from data analysis to web development to AI.
These beginner-friendly tutorials and courses help you sharpen the core skills you’ll use every day in Python, including loops, operators, lists, and functional techniques:
- How to Join Strings in Python
- Skip Ahead in Loops With Python’s continue Keyword
- Nested Loops in Python
- Python Operators and Expressions
- Exploring Python’s list Data Type With Examples
- Using Functional Programming in Python
If you’re looking to accelerate your learning with live instruction, then check out our Python for Beginners Live Course, where you can learn alongside other students and get real-time feedback from experienced instructors.
And don’t forget to grab our downloadable Python 3 Cheat Sheet to keep essential syntax and concepts at your fingertips:
Explore What’s New in Python 3.14
Python 3.14 brought a collection of thoughtful improvements that make writing and debugging code more enjoyable. The REPL now feels like a proper modern shell, making third-party REPLs like IPython, ptpython, and bpython less necessary. Type hints behave more intuitively, error messages point you in the right direction faster, and t-strings open up new possibilities for string processing.
These tutorials walk you through the updates you’ll want to start using right away:
- Python 3.14: Cool New Features for You to Try
- Python 3.14: Lazy Annotations
- Python 3.14: REPL Autocompletion and Highlighting
- Python 3.14: Better Syntax Error Messages
- Python 3.14: Template Strings (T-Strings)
If you’re curious about any other new features, you can also read the 3.14 release notes. Staying current with new Python releases helps you write cleaner code and take advantage of performance improvements as they land.
Read the full article at https://realpython.com/popular-python-tutorials-2025/ »
[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]































