skip to navigation
skip to content

Planet Python

Last update: January 07, 2026 07:44 PM UTC

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:

A Python Learning Roadmap Sheet that you can fill and print

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.

You can download a Personal Python Learning Roadmap cheat sheet to help you create your plan by clicking the link below:

Get Your Python Learning Roadmap: Click here to download your free personal Python learning roadmap PDF that you can fill to 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 cheat sheet 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 ]

January 07, 2026 02:00 PM UTC


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.

January 07, 2026 12:00 AM UTC

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 »

The PyCoder’s Weekly Logo


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

alt

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

onlymaps: A Python Micro-ORM

GITHUB.COM/MANOSS96

toon-formatter-py: TOON Data Formatting Library

GITHUB.COM/ANKITPAL181

Liberty Mail: Email Client for Sales Outreach

GITHUB.COM/EYEOFLIBERTY • Shared by [Ivan Kuzmin]

django-new: Create Django Applications With Pizzazz

GITHUB.COM/ADAMGHILL

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 »

alt

[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]

January 06, 2026 07:30 PM UTC


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

January 06, 2026 06:00 PM UTC


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:


[ 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 06, 2026 02:00 PM UTC


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 is 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 14 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 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
  • Cabbage
  • 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
  • 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 Cabbage, Cupcake, French Fries, Fruit Basket, Gelatin, Jelly Beans, 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
Cabbage
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
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 06, 2026 12:00 AM UTC

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:

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! ❤️

TabTab,

Your AI completion team

January 05, 2026 07:41 PM UTC


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 Test

Test your Python knowledge in a skills quiz with basic to advanced questions. Are you a Novice, Intermediate, Proficient, or Expert?

Strengthen Your Python Foundations

Operators and Expressions in Python

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:

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:

📎 Free Bonus: Python Cheat Sheet (PDF) Click to preview & download
Preview of the Python Cheat Sheet PDF

Learn Python 3 fundamentals at a glance: data types, functions, classes, and more!

No spam. Unsubscribe any time.

Explore What’s New in Python 3.14

Python 3.14: Cool New Features for You to Try

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:

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 ]

January 05, 2026 02:00 PM UTC

Quiz: How to Convert Bytes to Strings in Python

In this quiz, you’ll test your understanding of the Convert Python Bytes to Strings tutorial.

By working through this quiz, you’ll revisit how to decode bytes, choose an encoding, and turn binary data into readable text.

You’ll also see how encodings map byte values to characters so you can handle data from files, databases, and APIs with confidence. For a deeper dive, check out Python Encodings: A Guide and Python Bytes.


[ 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 05, 2026 12:00 PM UTC

Quiz: How to Properly Indent Python Code

In this quiz, you’ll test your understanding of How to Indent in Python.

By working through this quiz, you’ll revisit how to properly indent Python code, choose helpful editor settings, use spaces properly, apply code formatters, and explain why indentation is required.


[ 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 05, 2026 12:00 PM UTC


Patrick Müller

Static Protocols in Python: Behaviour Over Inheritance

The first time I read about protocols was in the book "Fluent Python" by Luciano Ramalho. This book goes deep. Deeper than I knew Python at that time. If you hadn&apost heard of Protocols before, I&aposll give you a short introduction.

Protocols have something to do with typing. With protocols, you can check whether an object is valid based on whether it has the right methods and attributes. The idea is to check for behaviour instead of inheritance. Protocols extend Python&aposs type hints by allowing to define structural types. They can be very confusing at first and difficult to understand, especially in a real-world scenario. In my opinion, that&aposs partly because, for the concept to click, one must mentally move away from pure object-oriented programming. This is mostly done with inheritance. But it&aposs also difficult to understand, because I think it&aposs an advanced concept. Typing is also a gradually evolving topic in Python, with a naming scheme that has also evolved gradually and which is sometimes difficult to grasp, too.

Generally, we distinguish between dynamic and static protocols. This article is about static protocols.

What&aposs mostly used - Goose Typing

It&aposs best to start with a short example. Before I knew protocols, I had used isinstance to check for a type. This is also called goose typing. Mostly this is used when one wants to check if an object is of a specific type during runtime. During runtime is important here. Here&aposs an example:

def fly_to_moon(something):
  if isinstance(something, Spaceship)
    spaceship.fly()
  else:
    print("Ain&apost flying to the moon with this")

Usually, goose typing is used with ABCs and abstract classes. The concept aligns very well with the human brain. With inheritance, we can bring structure to chaos. It&aposs neat. Most of the time, developers think about the domain they develop functions for, and then come up with an inheritance tree that&aposs very carefully crafted for that domain. In the scenario above, we&aposd have a base class of Plane and a class Spaceship that inherits from Plane. It inherits a fly method along. What I do not like about this is that, in application software, I do not want to limit the possibilities upfront by using inheritance. Secondly, with inheritance, we introduce tight coupling, which is a drawback.

Static Protocols

Protocols come with the idea that an object should behave in a certain way (structural typing), rather than implementing a certain interface or inheriting from a base class, which is also called nominal typing.

Short Intro

alt

Structural means we care about the behaviour. So, as long as we have an object that supports a fly function, we are pretty happy with it and think, "Okay, you apparently have all you need, let&aposs go". Here&aposs how we would adjust our example from above to use protocols:

from typing import Protocol

class MoonFlyable(Protocol):
  def fly(self): …
  
class Superman:
  def fly(self):
    print(&aposI am not a plane but still able to fly to the moon 🚀&apos)

def fly_to_moon(who_knows: MoonFlyable):
  who_knows.fly()

Now, our construct is not only cleaner than goose typing and reduces coupling, but also enables static type checkers like MyPy, PyRight, or ty to pick up on compatibility before we run the code. 

While you can write fly_to_moon(new Superman()) , a static type checker will complain about fly_to_moon(new Car()) and mark it as invalid.

Remember, all we ask for is a specific behaviour to be fulfilled. This gives us greater flexibility in our software design. Here&aposs my take on illustrating this:

altSuperman is able to fly to the moon ...

That example is a bit artificial, but serves perfectly as a short intro.

Real-World Example

Real world code is mostly more complex than this, and when I read tutorials, I most often wish to see something a bit more difficult.

As a software engineer in the AI field, I sometimes need load different trained models for certain projects. A very basic machine learning use case is to predict a class, and we have different models that predict different classes (e.g. cats or dogs, not Python classes) in different ways. You often see ABC here, but, as I said before, I think you lose flexibility this way. With protocols, each developer on the team can create, train and predict without extending an inheritance tree. I prefer to program functionally as I favour composition over inheritance, but others can code object-oriented and the code remains compatible.

Here&aposs how this looks like:

from typing import Protocol

class Predictable(Protocol):
    """Protocol for models that can make predictions (Used for API)."""

    def predict_top_k(self, query: str, k: int) -> list[str]: 
      ...

class TfidfPredictor:
    """Predictor class for TF-IDF model to get top-k predictions."""

    def __init__(self, model):
        self.model = model

    def predict_top_k(self, query: str, k: int) -> list[str]:
        """Predict top-k classes and their probabilities."""
        # code ommitted because not relevant here


tfidf_model = load_model(model_path)
ml_models: dict[str, Predictable] = {}
ml_models[&apostfidf&apos] = TfidfPredictor(tfidf_model)

The TfidfPredictor is really just a wrapper that contains only the predict_top_k function the protocol wants to see. This demonstrates how I would store loaded models in a dictionary, using the Predictable protocol as the value type. We can then run the predictions in a &aposduck typing&apos manner based on this.

Alternatively, we could use the protocol class for our inference method.

tfidf_model = load_model(model_path)
predictor = TfidfPredictor(tfidf_model)

def run_inference(model: Predictable, query: str, k: int) -> list[str]:
    return model.predict_top_k(query, k)

y_pred_k = run_inference(model=predictor, query="Classify this text", k=5)

Now, if you look at the code, you see that the TfidfPredictor is completely independent of implementing any ABC. All we care about is that the model we pass to our dictionary or to the run_inference function is compliant with the Predictable protocol. 

Limitations of Static Protocols

Keep in mind that protocols do not guarantee that, at runtime, the type is enforced. If you need to check during runtime, you would need to decorate your defined protocol with @runtime_checkable. Then, you could use isinstance again: isinstance(obj, Predictable).

Small Detour to Duck Typing and Static vs. Dynamic Protocols

When I say duck typing, it has to come with the very famous saying: "If it walks like a duck and it quacks like a duck, then it must be a duck.". This saying provides you the interpreter&aposs view on an object. As long as an object provides the required behaviour (e.g. the quack() method), it will be used as a duck, regardless of its actual type. This should sound familiar to you from static protocols. Both are behavioural. The main difference is that your IDE or a static type checker won&apost give you any hint about the correctness of the signature. Here&aposs an adapted example from the Duck Typing article on Real Python:

Duck Typing in Python: Writing Flexible and Decoupled Code – Real Python
In this tutorial, you’ll learn about duck typing in Python. It’s a typing system based on objects’ behaviors rather than on inheritance. By taking advantage of duck typing, you can create flexible and decoupled sets of Python classes that you can use together or individually.
alt
class Duck:
    def fly(self):
        print("The duck is flying")


class Swan:
    def fly(self, height: int):
        print("The swan is flying")


birds = [Duck(), Swan()]

for bird in birds:
    bird.fly()

In this example, my static type checker (Pyright) doesn&apost tell me that we need a height for the Swan object to run the fly method. Duck typing is used with EAFP, and I like it. Static protocols extend the idea and make it safer to program, catching possible runtime errors during development time and not runtime. Small side note: Structural (sub)typing is also called static duck typing.

Conclusion

This article was about static protocols, also called static duck typing. It&aposs baked in Python since version 3.8. Before that, we&aposd had already informal interfaces, also called dynamic protocols. The naming is sometimes confusing, and I have to look it up from time to time myself.

The difference is that a dynamic protocol does not have to be fully implemented and it cannot be verified by a static type checker. An example of a dynamic protocol is the Sequence protocol, which requires the __getitem__ and the __len__ ; however, it also works only with __getitem__.

To me, the main advantage of static protocols is the ability to write loose coupling code, not to run in an increasingly complex construct of inheritance, and, most importantly, to provide a way to specify a specific behaviour (structural types) instead of nominal types. Hence, in a project, it becomes easier to bring different developer styles and libraries together. One might write more functional code, another more object oriented. Besides that, it&aposs cumbersome to make different libraries (machine learning world) work with each other in own pipelines.

If you want to read more about static protocols, I can recommend the original PEP 544 – Protocols: Structural subtyping (static duck typing) .

This was a short primer on using static protocols in a real-world project. Ever run into issues with ABCs (too big to touch, too complex?!)? Let me know!


Thank you for reading! 💙 I share what I learn about software engineering (mostly Python), AI, product development, and life as a dev.

Subscribe and follow me on my journey. No spam. Unsubscribe anytime.

Get notified
🤗
If you enjoyed this article, it would mean a lot to me if you shared it on social media or forwarded it to a friend. I write in my spare time, so any support is welcome.
.share-buttons { display: flex; gap: 8px; margin-top: 8px; justify-content: center; } .share-button { width: 34px; height: 34px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; text-decoration: none; cursor: pointer; line-height: 0; /* remove inline SVG baseline whitespace */ border: none; box-shadow: 0 1px 2px rgba(0,0,0,0.06); transition: transform .12s ease, box-shadow .12s ease; } .share-button svg { width: 14px; height: 14px; fill: white; display: block; /* prevents baseline alignment issues */ } /* brand backgrounds */ .share-button.mastodon { background: #6364FF; } .share-button.linkedin { background: #0A66C2; } .share-button.bluesky { background: #1185FE; } .share-button:hover { transform: translateY(-1px); box-shadow: 0 3px 8px rgba(0,0,0,0.12); } .share-button:focus { outline: 3px solid rgba(21,156,228,0.25); outline-offset: 2px; } /* optional small tweak for very small screens */ @media (max-width: 320px) { .share-button { width: 30px; height: 30px; } .share-button svg { width: 12px; height: 12px; } }

January 05, 2026 09:23 AM UTC


Talk Python to Me

#533: Web Frameworks in Prod by Their Creators

Today on Talk Python, the creators behind FastAPI, Flask, Django, Quart, and Litestar get practical about running apps based on their framework in production. Deployment patterns, async gotchas, servers, scaling, and the stuff you only learn at 2 a.m. when the pager goes off. For Django, we have Carlton Gibson and Jeff Triplet. For Flask, we have David Lord and Phil Jones, and on team Litestar we have Janek Nouvertné and Cody Fincher, and finally Sebastián Ramírez from FastAPI is here. Let’s jump in.<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>Carlton Gibson - Django</strong>: <a href="https://github.com/carltongibson?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Sebastian Ramirez - FastAPI</strong>: <a href="https://github.com/tiangolo?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>David Lord - Flask</strong>: <a href="https://davidism.com?featured_on=talkpython" target="_blank" >davidism.com</a><br/> <strong>Phil Jones - Flask and Quartz(async)</strong>: <a href="https://pgjones.dev?featured_on=talkpython" target="_blank" >pgjones.dev</a><br/> <strong>Yanik Nouvertne - LiteStar</strong>: <a href="https://github.com/provinzkraut?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Cody Fincher - LiteStar</strong>: <a href="https://github.com/cofin?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Jeff Triplett - Django</strong>: <a href="https://jefftriplett.com?featured_on=talkpython" target="_blank" >jefftriplett.com</a><br/> <br/> <strong>Django</strong>: <a href="https://www.djangoproject.com?featured_on=talkpython" target="_blank" >www.djangoproject.com</a><br/> <strong>Flask</strong>: <a href="https://flask.palletsprojects.com/en/latest/?featured_on=talkpython" target="_blank" >flask.palletsprojects.com</a><br/> <strong>Quart</strong>: <a href="https://quart.palletsprojects.com/en/latest/?featured_on=talkpython" target="_blank" >quart.palletsprojects.com</a><br/> <strong>Litestar</strong>: <a href="https://litestar.dev?featured_on=talkpython" target="_blank" >litestar.dev</a><br/> <strong>FastAPI</strong>: <a href="https://fastapi.tiangolo.com?featured_on=talkpython" target="_blank" >fastapi.tiangolo.com</a><br/> <strong>Coolify</strong>: <a href="https://coolify.io?featured_on=talkpython" target="_blank" >coolify.io</a><br/> <strong>ASGI</strong>: <a href="https://asgi.readthedocs.io/en/latest/?featured_on=talkpython" target="_blank" >asgi.readthedocs.io</a><br/> <strong>WSGI (PEP 3333)</strong>: <a href="https://peps.python.org/pep-3333/?featured_on=talkpython" target="_blank" >peps.python.org</a><br/> <strong>Granian</strong>: <a href="https://github.com/emmett-framework/granian?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Hypercorn</strong>: <a href="https://github.com/pgjones/hypercorn?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>uvicorn</strong>: <a href="https://uvicorn.dev?featured_on=talkpython" target="_blank" >uvicorn.dev</a><br/> <strong>Gunicorn</strong>: <a href="https://gunicorn.org/?featured_on=talkpython" target="_blank" >gunicorn.org</a><br/> <strong>Hypercorn</strong>: <a href="https://hypercorn.readthedocs.io/en/latest/?featured_on=talkpython" target="_blank" >hypercorn.readthedocs.io</a><br/> <strong>Daphne</strong>: <a href="https://github.com/django/daphne?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Nginx</strong>: <a href="https://nginx.org/?featured_on=talkpython" target="_blank" >nginx.org</a><br/> <strong>Docker</strong>: <a href="https://www.docker.com/?featured_on=talkpython" target="_blank" >www.docker.com</a><br/> <strong>Kubernetes</strong>: <a href="https://kubernetes.io/?featured_on=talkpython" target="_blank" >kubernetes.io</a><br/> <strong>PostgreSQL</strong>: <a href="https://www.postgresql.org/?featured_on=talkpython" target="_blank" >www.postgresql.org</a><br/> <strong>SQLite</strong>: <a href="https://www.sqlite.org/?featured_on=talkpython" target="_blank" >www.sqlite.org</a><br/> <strong>Celery</strong>: <a href="https://docs.celeryq.dev/?featured_on=talkpython" target="_blank" >docs.celeryq.dev</a><br/> <strong>SQLAlchemy</strong>: <a href="https://www.sqlalchemy.org/?featured_on=talkpython" target="_blank" >www.sqlalchemy.org</a><br/> <strong>Django REST framework</strong>: <a href="https://www.django-rest-framework.org/?featured_on=talkpython" target="_blank" >www.django-rest-framework.org</a><br/> <strong>Jinja</strong>: <a href="https://jinja.palletsprojects.com/?featured_on=talkpython" target="_blank" >jinja.palletsprojects.com</a><br/> <strong>Click</strong>: <a href="https://click.palletsprojects.com/?featured_on=talkpython" target="_blank" >click.palletsprojects.com</a><br/> <strong>HTMX</strong>: <a href="https://htmx.org/?featured_on=talkpython" target="_blank" >htmx.org</a><br/> <strong>Server-Sent Events (SSE)</strong>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events?featured_on=talkpython" target="_blank" >developer.mozilla.org</a><br/> <strong>WebSockets (RFC 6455)</strong>: <a href="https://www.rfc-editor.org/rfc/rfc6455?featured_on=talkpython" target="_blank" >www.rfc-editor.org</a><br/> <strong>HTTP/2 (RFC 9113)</strong>: <a href="https://www.rfc-editor.org/rfc/rfc9113?featured_on=talkpython" target="_blank" >www.rfc-editor.org</a><br/> <strong>HTTP/3 (RFC 9114)</strong>: <a href="https://www.rfc-editor.org/rfc/rfc9114?featured_on=talkpython" target="_blank" >www.rfc-editor.org</a><br/> <strong>uv</strong>: <a href="https://docs.astral.sh/uv/?featured_on=talkpython" target="_blank" >docs.astral.sh</a><br/> <strong>Amazon Web Services (AWS)</strong>: <a href="https://aws.amazon.com/?featured_on=talkpython" target="_blank" >aws.amazon.com</a><br/> <strong>Microsoft Azure</strong>: <a href="https://azure.microsoft.com/?featured_on=talkpython" target="_blank" >azure.microsoft.com</a><br/> <strong>Google Cloud Run</strong>: <a href="https://cloud.google.com/run?featured_on=talkpython" target="_blank" >cloud.google.com</a><br/> <strong>Amazon ECS</strong>: <a href="https://aws.amazon.com/ecs/?featured_on=talkpython" target="_blank" >aws.amazon.com</a><br/> <strong>AlloyDB for PostgreSQL</strong>: <a href="https://cloud.google.com/alloydb?featured_on=talkpython" target="_blank" >cloud.google.com</a><br/> <strong>Fly.io</strong>: <a href="https://fly.io/?featured_on=talkpython" target="_blank" >fly.io</a><br/> <strong>Render</strong>: <a href="https://render.com/?featured_on=talkpython" target="_blank" >render.com</a><br/> <strong>Cloudflare</strong>: <a href="https://www.cloudflare.com/?featured_on=talkpython" target="_blank" >www.cloudflare.com</a><br/> <strong>Fastly</strong>: <a href="https://www.fastly.com/?featured_on=talkpython" target="_blank" >www.fastly.com</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=cHmoClKu6qk" target="_blank" >youtube.com</a><br/> <strong>Episode #533 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/533/web-frameworks-in-prod-by-their-creators#takeaways-anchor" target="_blank" >talkpython.fm/533</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/533/web-frameworks-in-prod-by-their-creators" 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 05, 2026 08:00 AM UTC


Python Bytes

#464 Malicious Package? No Build For You!

<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://astral.sh/blog/ty?featured_on=pythonbytes">ty: An extremely fast Python type checker and LSP</a></strong></li> <li><strong><a href="https://mkennedy.codes/posts/python-supply-chain-security-made-easy/?featured_on=pythonbytes">Python Supply Chain Security Made Easy</a></strong></li> <li><strong><a href="https://typing-extensions.readthedocs.io/en/latest/?featured_on=pythonbytes">typing_extensions</a></strong></li> <li><strong><a href="https://www.theregister.com/2025/12/16/mi6_chief_well_be_as/?featured_on=pythonbytes">MI6 chief: We'll be as fluent in Python as we are in Russian</a></strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=ZUFz1WkCZCg' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="464">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <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 10am 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>Brian #1: <a href="https://astral.sh/blog/ty?featured_on=pythonbytes">ty: An extremely fast Python type checker and LSP</a></strong></p> <ul> <li>Charlie Marsh announced the Beta release of <code>ty</code> on Dec 16</li> <li>“designed as an alternative to tools like mypy, Pyright, and Pylance.”</li> <li>Extremely fast even from first run</li> <li>Successive runs are incremental, only rerunning necessary computations as a user edits a file or function. This allows live updates.</li> <li>Includes nice visual diagnostics much like color enhanced tracebacks</li> <li>Extensive configuration control <ul> <li>Nice for if you want to gradually fix warnings from ty for a project</li> </ul></li> <li>Also released a nice <a href="https://marketplace.visualstudio.com/items?itemName=astral-sh.ty&featured_on=pythonbytes">VSCode (or Cursor) extension</a> <ul> <li>Check the docs. There are lots of features.</li> <li>Also a note about disabling the default language server (or disabling ty’s language server) so you don’t have 2 running</li> </ul></li> </ul> <p><strong>Michael #2:</strong> <a href="https://mkennedy.codes/posts/python-supply-chain-security-made-easy/?featured_on=pythonbytes">Python Supply Chain Security Made Easy</a></p> <ul> <li>We know about supply chain security issues, but what can you do? <ul> <li>Typosquatting (not great)</li> <li>Github/PyPI account take-overs (very bad)</li> </ul></li> <li>Enter <a href="https://github.com/pypa/pip-audit?featured_on=pythonbytes">pip-audit</a>.</li> <li>Run it in two ways: <ol> <li>Against your installed dependencies in current venv</li> <li>As a proper unit test (so when running pytest or CI/CD).</li> <li>Let others find out first, wait a week on all dependency updates: <code>uv pip compile requirements.piptools --upgrade --output-file requirements.txt --exclude-newer "1 week"</code></li> </ol></li> <li>Follow up article: <a href="https://mkennedy.codes/posts/devops-python-supply-chain-security/?featured_on=pythonbytes">DevOps Python Supply Chain Security</a> <ol> <li>Create a dedicated Docker image for testing dependencies with pip-audit in isolation before installing them into your venv. <ol> <li>Run pip-compile / uv lock --upgrade to generate the new lock file</li> <li>Test in a ephemeral pip-audit optimized Docker container</li> <li>Only then if things pass, uv pip install / uv sync</li> </ol></li> <li>Add a dedicated Docker image build step that fails the <code>docker build</code> step if a vulnerable package is found.</li> </ol></li> </ul> <p><strong>Brian #3: <a href="https://typing-extensions.readthedocs.io/en/latest/?featured_on=pythonbytes">typing_extensions</a></strong></p> <ul> <li>Kind of a followup on the deprecation warning topic we were talking about in December.</li> <li><a href="https://hachyderm.io/@prioinv/115767086197017938?featured_on=pythonbytes">prioinv</a> on Mastodon notified us that the project <a href="https://typing-extensions.readthedocs.io/en/latest/#typing_extensions.deprecated">typing-extensions</a> includes it as part of the backport set.</li> <li>The warnings.deprecated decorator is new to Python 3.13, but with <code>typing-extensions</code>, you can use it in previous versions.</li> <li>But <code>typing_extesions</code> is way cooler than just that.</li> <li>The module serves 2 purposes: <ul> <li>Enable use of new type system features on older Python versions.</li> <li>Enable experimentation with type system features proposed in new PEPs before they are accepted and added to the <a href="https://docs.python.org/3.14/library/typing.html#module-typing"><strong>&lt;code>typing&lt;/code></strong></a> module.</li> </ul></li> <li>So cool.</li> <li>There’s a lot of features here. I’m hoping it allows someone to use the latest typing syntax across multiple Python versions.</li> <li>I’m “tentatively” excited. But I’m bracing for someone to tell me why it’s not a silver bullet.</li> </ul> <p><strong>Michael #4:</strong> <a href="https://www.theregister.com/2025/12/16/mi6_chief_well_be_as/?featured_on=pythonbytes">MI6 chief: We'll be as fluent in Python as we are in Russian</a></p> <ul> <li>"Advances in artificial intelligence, biotechnology and quantum computing are not only revolutionizing economies but rewriting the reality of conflict, as they 'converge' to create science fiction-like tools,” said new MI6 chief Blaise Metreweli.</li> <li>She focused mainly on threats from Russia, the country is "testing us in the grey zone with tactics that are just below the threshold of war.”</li> <li>This demands what she called "mastery of technology" across the service, with officers required to become "as comfortable with lines of code as we are with human sources, as fluent in Python as we are in multiple other languages."</li> <li>Recruitment will target linguists, data scientists, engineers, and technologists alike.</li> </ul> <p><strong>Extras</strong></p> <p>Brian:</p> <ul> <li>Next chapter of Lean TDD being released today, Finding Waste in TDD <ul> <li>Still going to attempt a Jan 31 deadline for first draft of book.</li> <li>That really doesn’t seem like enough time, but I’m optimistic.</li> </ul></li> <li><a href="https://www.steamdeck.com/en/?featured_on=pythonbytes">SteamDeck</a> is not helping me find time to write <ul> <li>But I very much appreciate the gift from my fam</li> <li>Send me game suggestions on Mastodon or Bluesky. I’d love to hear what you all are playing.</li> </ul></li> </ul> <p>Michael:</p> <ul> <li>Astral has announced the Beta release of ty, which they say they are "ready to recommend to motivated users for production use." <ul> <li><a href="https://astral.sh/blog/ty?featured_on=pythonbytes">Blog post</a></li> <li><a href="https://github.com/astral-sh/ty/releases/tag/0.0.2?featured_on=pythonbytes">Release page</a></li> </ul></li> <li>Reuven Lerner has <a href="https://www.youtube.com/watch?v=TCSjgvtO714&list=PLbFHh-ZjYFwFWHVT0qeg9Jz1TBD0TlJJT">a video series on Pandas 3</a></li> </ul> <p><strong>Joke:</strong> <a href="https://x.com/pr0grammerhum0r/status/1998618710408315372?s=12&featured_on=pythonbytes">Error Handling in the age of AI</a></p> <ul> <li>Play on the inversion of <a href="https://www.flickr.com/photos/nathansmith/4704268314/?featured_on=pythonbytes">JavaScript the Good Parts</a></li> </ul>

January 05, 2026 08:00 AM UTC


Glyph Lefkowitz

How To Argue With Me About AI, If You Must

As you already know if you’ve read any of this blog in the last few years, I am a somewhat reluctant — but nevertheless quite staunch — critic of LLMs. This means that I have enthusiasts of varying degrees sometimes taking issue with my stance.

It seems that I am not going to get away from discussions, and, let’s be honest, pretty intense arguments about “AI” any time soon. These arguments are starting to make me quite upset. So it might be time to set some rules of engagement.

I’ve written about all of these before at greater length, but this is a short post because it’s not about the technology or making a broader point, it’s about me. These are rules for engaging with me, personally, on this topic. Others are welcome to adopt these rules if they so wish but I am not encouraging anyone to do so.

Thus, I’ve made this post as short as I can so everyone interested in engaging can read the whole thing. If you can’t make it through to the end, then please just follow Rule Zero.

Rule Zero: Maybe Don’t

You are welcome to ignore me. You can think my take is stupid and I can think yours is. We don’t have to get into an Internet Fight about it; we can even remain friends. You do not need to instigate an argument with me at all, if you think that my analysis is so bad that it doesn’t require rebutting.

Rule One: No ‘Just’

As I explained in a post with perhaps the least-predictive title I’ve ever written, “I Think I’m Done Thinking About genAI For Now”, I’ve already heard a bunch of bad arguments. Don’t tell me to ‘just’ use a better model, use an agentic tool, use a more recent version, or use some prompting trick that you personally believe works better. If you skim my work and think that I must not have deeply researched anything or read about it because you don’t like my conclusion, that is wrong.

Rule Two: No ‘Look At This Cool Thing’

Purely as a productivity tool, I have had a terrible experience with genAI. Perhaps you have had a great one. Neat. That’s great for you. As I explained at great length in “The Futzing Fraction”, my concern with generative AI is that I believe it is probably a net negative impact on productivity, based on both my experience and plenty of citations. Go check out the copious footnotes if you’re interested in more detail.

Therefore, I have already acknowledged that you can get an LLM to do various impressive, cool things, sometimes. If I tell you that you will, on average, lose money betting on a slot machine, a picture of a slot machine hitting a jackpot is not evidence against my position.

Rule Two And A Half: Engage In Metacognition

I specifically didn’t title the previous rule “no anecdotes” because data beyond anecdotes may be extremely expensive to produce. I don’t want to say you can never talk to me unless you’re doing a randomized controlled trial. However, if you are going to tell me an anecdote about the way that you’re using an LLM, I am interested in hearing how you are compensating for the well-documented biases that LLM use tends to induce. Try to measure what you can.

Rule Three: Do Not Cite The Deep Magic To Me

As I explained in “A Grand Unified Theory of the AI Hype Cycle”, I already know quite a bit of history of the “AI” label. If you are tempted to tell me something about how “AI” is really such a broad field, and it doesn’t just mean LLMs, especially if you are trying to launder the reputation of LLMs under the banner of jumbling them together with other things that have been called “AI”, I assure you that this will not be convincing to me.

Rule Four: Ethics Are Not Optional

I have made several arguments in my previous writing: there are ethical arguments, efficacy arguments, structuralist arguments, efficiency arguments and aesthetic arguments.

I am happy to, for the purposes of a good-faith discussion, focus on a specific set of concerns or an individual point that you want to make where you think I got something wrong. If you convince me that I am entirely incorrect about the effectiveness or predictability of LLMs in general or as specific LLM product, you don’t need to make a comprehensive argument about whether one should use the technology overall. I will even assume that you have your own ethical arguments.

However, if you scoff at the idea that one should have any ethical boundaries at all, and think that there’s no reason to care about the overall utilitarian impact of this technology, that it’s worth using no matter what else it does as long as it makes you 5% better at your job, that’s sociopath behavior.

This includes extreme whataboutism regarding things like the water use of datacenters, other elements of the surveillance technology stack, and so on.


Consequences

These are rules, once again, just for engaging with me. I have no particular power to enact broader sanctions upon you, nor would I be inclined to do so if I could. However, if you can’t stay within these basic parameters and you insist upon continuing to direct messages to me about this topic, I will summarily block you with no warning, on mastodon, email, GitHub, IRC, or wherever else you’re choosing to do that. This is for your benefit as well: such a discussion will not be a productive use of either of our time.

January 05, 2026 05:22 AM UTC


Zato Blog

Python scheduler for API integrations

Python scheduler for API integrations

Are you looking for a practical way to automate API tasks with reliable scheduling? This tutorial demonstrates how to implement task scheduling using Zato within Docker.

The guide focuses on real-world implementation rather than theory, showing you how to build scheduling solutions that work consistently in production environments.

What you'll learn in the tutorial:


Python Scheduler

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
What is a Network Packet Broker? How to automate networks in Python?

January 05, 2026 03:00 AM UTC

January 04, 2026


Python Morsels

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.

Table of contents

  1. A broken Python program
  2. Troubleshooting with print
  3. Using self-documenting expressions
  4. Self-documenting expressions work with any expression
  5. Using format specifiers with self-documenting expressions
  6. Making more readable self-documenting expressions
  7. Summary

A broken Python program

Here we have a program that makes a random math prompt and then validates whether the answer give by the user is correct:

import random

x = random.randrange(1, 10)
y = random.randrange(1, 10)

answer = input(f"What's {x} multiplied by {y}? ")
expected = x * y

if answer == expected:
    print("Correct!")
else:
    print("That's incorrect")

This program doesn't work right now:

$ python3 check_mult.py
What's 9 multiplied by 8? 72
That's incorrect

Our program always tells us that our answer is incorrect.

Troubleshooting with print

Now, we could try to …

Read the full article: https://www.pythonmorsels.com/debugging-with-f-strings/

January 04, 2026 05:51 PM UTC


Artem Golubin

Recent optimizations in Python's Reference Counting

It's been a while since I've written about CPython internals and its optimizations. My last article on garbage collection was written 8 years ago.

A lot of small optimizations were added since then. In this article, I will highlight a new optimization for reference counting that uses a static lifetime analysis.

Background on reference counting in CPython

Reference counting is the primary memory management technique used in CPython.

In short, every Python object (the actual value behind a variable) has a reference counter field that tracks how many references point to it. When an object's reference count drops to zero, the memory occupied by that object is immediately deallocated.

For hot loops, this can lead to significant overhead due to the frequent incrementing and decrementing of reference counts.[....]

January 04, 2026 05:14 PM UTC


EuroPython

Humans of EuroPython: Marina Moro López

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 Marina Moro López, a member of the EuroPython 2025 Programme Team and a former EuroPython speaker.

Thank you for contributing to the conference programme, Marina!

altMarina Moro López, member of the Programme Team at EuroPython 2025

EP: What first inspired you to volunteer for EuroPython? And which edition of the conference was it?

I volunteered at EuroPython 2025 because I was a speaker at the 2024 edition and fell in love with the event, so I wanted to do my bit to help keep it amazing.

EP: What was your primary role as a volunteer, and what did a typical day look like for you?

I was involved in reviewing talks and putting together the schedule, as well as contacting keynote speakers and organizing the open spaces. A typical day was filled with Excel spreadsheets and emails :) 

EP: Could you share your favorite memory from contributing to EuroPython?

Honestly, the day the program team got together at the event. We shared an intimate moment exclusively for ourselves after all the hard work we had done, seeing how it was paying off.

EP: Is there anything that surprised you about the volunteer experience?

It may seem that organizing such a large event can be chaotic at times with so many people involved, so I was surprised to see that this wasn’t the case at all and that, in the end, we were all one big team.

EP: How has contributing to EuroPython impacted your own career or learning journey?

Without a doubt, an event like EuroPython gives you top communication and organizational skills. Also, in my particular case, and maybe this is a little silly, I am super proud to say that I did my first PR ever!

EP: What&aposs one misconception about conference volunteering you&aposd like to clear up?

Even if you don&apost have a tech background (like me), if you want to help, that&aposs reason enough to participate. You don’t need anything else.

EP: If you were to invite someone else, what do you think are the top 3 reasons to join the EuroPython organizing team?

Seeing how an event like this is created from the inside is incredible, plus the team is lovely, and you&aposll learn a lot because you’ll be surrounded by top people from the community.

EP: Thank you for your work, Marina!

January 04, 2026 02:26 AM UTC

January 03, 2026


Hugo van Kemenade

Localising xkcd

I gave a lightning talk at a bunch of conferences in 2025 about some of the exciting new things coming in Python 3.14, including template strings.

One thing we can use t-strings for is to prevent SQL injection. The user gives you an untrusted t-string, and you can sanitise it, before using it in a safer way.

I illustrated this with xkcd 327, titled “Exploits of a Mom”, but commonly known as “Little Bobby Tables”.

I localised most of the slides for the PyCon I was at, including this comic. Here they are!

PyCon Italia #

May, Bologna

Did you really name your son Roberto’); DROP TABLE Students;– ? Oh, yes. Piccolo Berto Tables, we call him.

PyCon Greece #

August, Athens

Did you really name your son Κωνσταντίνος’); DROP TABLE Students;– ? Oh, yes. Μικρός Ντίνος Tables, we call him.

PyCon Estonia #

October, Tallinn

Did you really name your son Raivo’); DROP TABLE Students;– ? Oh, yes. Pisikene Tables, we call him.

PyCon Finland #

October, Jyväskylä

Did you really name your son Juhani’); DROP TABLE Students;– ? Oh, yes. Pikku Jussi, we call him.

PyCon Sweden #

October, Stockholm

Did you really name your son Oskar’); DROP TABLE Students;– ? Oh, yes. Lille Ogge Bord, we call him.

Thanks #

Thanks to Randall Munroe for licensing the comic under the Creative Commons Attribution-NonCommercial 2.5 License. These adaptations are therefore licensed the same way.

Finally, here’s links for 2026, I recommend them all:

January 03, 2026 02:06 PM UTC

January 02, 2026


Real Python

The Real Python Podcast – Episode #278: 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.


[ 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 02, 2026 12:00 PM UTC


Glyph Lefkowitz

The Next Thing Will Not Be Big

The dawning of a new year is an opportune moment to contemplate what has transpired in the old year, and consider what is likely to happen in the new one.

Today, I’d like to contemplate that contemplation itself.


The 20th century was an era characterized by rapidly accelerating change in technology and industry, creating shorter and shorter cultural cycles of changes in lifestyles. Thus far, the 21st century seems to be following that trend, at least in its recently concluded first quarter.

The early half of the twentieth century saw the massive disruption caused by electrification, radio, motion pictures, and then television.

In 1971, Intel poured gasoline on that fire by releasing the 4004, a microchip generally recognized as the first general-purpose microprocessor. Popular innovations rapidly followed: the computerized cash register, the personal computer, credit cards, cellular phones, text messaging, the Internet, the web, online games, mass surveillance, app stores, social media.

These innovations have arrived faster than previous generations, but also, they have crossed a crucial threshold: that of the human lifespan.

While the entire second millennium A.D. has been characterized by a gradually accelerating rate of technological and social change — the printing press and the industrial revolution were no slouches, in terms of changing society, and those predate the 20th century — most of those changes had the benefit of unfolding throughout the course of a generation or so.

Which means that any individual person in any given century up to the 20th might remember one major world-altering social shift within their lifetime, not five to ten of them. The diversity of human experience is vast, but most people would not expect that the defining technology of their lifetime was merely the latest in a progression of predictable civilization-shattering marvels.

Along with each of these successive generations of technology, we minted a new generation of industry titans. Westinghouse, Carnegie, Sarnoff, Edison, Ford, Hughes, Gates, Jobs, Zuckerberg, Musk. Not just individual rich people, but entire new classes of rich people that did not exist before. “Radio DJ”, “Movie Star”, “Rock Star”, “Dot Com Founder”, were all new paths to wealth opened (and closed) by specific technologies. While most of these people did come from at least some level of generational wealth, they no longer came from a literal hereditary aristocracy.

To describe this new feeling of constant acceleration, a new phrase was coined: “The Next Big Thing”. In addition to denoting that some Thing was coming and that it would be Big (i.e.: that it would change a lot about our lives), this phrase also carries the strong implication that such a Thing would be a product. Not a development in social relationships or a shift in cultural values, but some new and amazing form of conveying salted meat paste or what-have-you, that would make whatever lucky tinkerer who stumbled into it into a billionaire — along with any friends and family lucky enough to believe in their vision and get in on the ground floor with an investment.

In the latter part of the 20th century, our entire model of capital allocation shifted to account for this widespread belief. No longer were mega-businesses built by bank loans, stock issuances, and reinvestment of profit, the new model was “Venture Capital”. Venture capital is a model of capital allocation explicitly predicated on the idea that carefully considering each bet on a likely-to-succeed business and reducing one’s risk was a waste of time, because the return on the equity from the Next Big Thing would be so disproportionately huge — 10x, 100x, 1000x – that one could afford to make at least 10 bad bets for each good one, and still come out ahead.

The biggest risk was in missing the deal, not in giving a bunch of money to a scam. Thus, value investing and focus on fundamentals have been broadly disregarded in favor of the pursuit of the Next Big Thing.

If Americans of the twentieth century were temporarily embarrassed millionaires, those of the twenty-first are all temporarily embarrassed FAANG CEOs.

The predicament that this tendency leaves us in today is that the world is increasingly run by generations — GenX and Millennials — with the shared experience that the computer industry, either hardware or software, would produce some radical innovation every few years. We assume that to be true.

But all things change, even change itself, and that industry is beginning to slow down. Physically, transistor density is starting to brush up against physical limits. Economically, most people are drowning in more compute power than they know what to do with anyway. Users already have most of what they need from the Internet.

The big new feature in every operating system is a bunch of useless junk nobody really wants and is seeing remarkably little uptake. Social media and smartphones changed the world, true, but… those are both innovations from 2008. They’re just not new any more.

So we are all — collectively, culturally — looking for the Next Big Thing, and we keep not finding it.

It wasn’t 3D printing. It wasn’t crowdfunding. It wasn’t smart watches. It wasn’t VR. It wasn’t the Metaverse, it wasn’t Bitcoin, it wasn’t NFTs1.

It’s also not AI, but this is why so many people assume that it will be AI. Because it’s got to be something, right? If it’s got to be something then AI is as good a guess as anything else right now.

The fact is, our lifetimes have been an extreme anomaly. Things like the Internet used to come along every thousand years or so, and while we might expect that the pace will stay a bit higher than that, it is not reasonable to expect that something new like “personal computers” or “the Internet”3 will arrive again.

We are not going to get rich by getting in on the ground floor of the next Apple or the next Google because the next Apple and the next Google are Apple and Google. The industry is maturing. Software technology, computer technology, and internet technology are all maturing.

There Will Be Next Things

Research and development is happening in all fields all the time. Amazing new developments quietly and regularly occur in pharmaceuticals and in materials science. But these are not predictable. They do not inhabit the public consciousness until they’ve already happened, and they are rarely so profound and transformative that they change everybody’s life.

There will even be new things in the computer industry, both software and hardware. Foldable phones do address a real problem (I wish the screen were even bigger but I don’t want to carry around such a big device), and would probably be more popular if they got the costs under control. One day somebody’s going to crack the problem of volumetric displays, probably. Some VR product will probably, eventually, hit a more realistic price/performance ratio where the niche will expand at least a little more.

Maybe there will even be something genuinely useful, which is recognizably adjacent to the current “AI” fad, but if it is, it will be some new development that we haven’t seen yet. If current AI technology were sufficient to drive some interesting product, it would already be doing it, not using marketing disguised as science to conceal diminishing returns on current investments.

But They Will Not Be Big

The impulse to find the One Big Thing that will dominate the next five years is a fool’s errand. Incremental gains are diminishing across the board. The markets for time and attention2 are largely saturated. There’s no need for another streaming service if 100% of your leisure time is already committed to TikTok, YouTube and Netflix; famously, Netflix has already considered sleep its primary competitor for close to a decade - years before the pandemic.

Those rare tech markets which aren’t saturated are suffering from pedestrian economic problems like wealth inequality, not technological bottlenecks.

For example, the thing preventing the development of a robot that can do your laundry and your dishes without your input is not necessarily that we couldn’t build something like that, but that most households just can’t afford it without wage growth catching up to productivity growth. It doesn’t make sense for anyone to commit to the substantial R&D investment that such a thing would take, if the market doesn’t exist because the average worker isn’t paid enough to afford it on top of all the other tech which is already required to exist in society.

The projected income from the tiny, wealthy sliver of the population who could pay for the hardware, cannot justify an investment in the software past a fake version remotely operated by workers in the global south, only made possible by Internet wage arbitrage, i.e. a more palatable, modern version of indentured servitude.

Even if we were to accept the premise of an actually-“AI” version of this, that is still just a wish that ChatGPT could somehow improve enough behind the scenes to replace that worker, not any substantive investment in a novel, proprietary-to-the-chores-robot software system which could reliably perform specific functions.

What, Then?

The expectation for, and lack of, a “big thing” is a big problem. There are others who could describe its economic, political, and financial dimensions better than I can. So then let me speak to my expertise and my audience: open source software developers.

When I began my own involvement with open source, a big part of the draw for me was participating in a low-cost (to the corporate developer) but high-value (to society at large) positive externality. None of my employers would ever have cared about many of the applications for which Twisted forms a core bit of infrastructure; nor would I have been able to predict those applications’ existence. Yet, it is nice to have contributed to their development, even a little bit.

However, it’s not actually a positive externality if the public at large can’t directly benefit from it.

When real world-changing, disruptive developments are occurring, the bean-counters are not watching positive externalities too closely. As we discovered with many of the other benefits that temporarily accrued to labor in the tech economy, Open Source that is usable by individuals and small companies may have been a ZIRP. If you know you’re gonna make a billion dollars you’re not going to worry about giving away a few hundred thousand here and there.

When gains are smaller and harder to realize, and margins are starting to get squeezed, it’s harder to justify the investment in vaguely good vibes.

But this, itself, is not a call to action. I doubt very much that anyone reading this can do anything about the macroeconomic reality of higher interest rates. The technological reality of “development is happening slower” is inherently something that you can’t change on purpose.

However, what we can do is to be aware of this trend in our own work.

Fight Scale Creep

It seems to me that more and more open source infrastructure projects are tools for hyper-scale application development, only relevant to massive cloud companies. This is just a subjective assessment on my part — I’m not sure what tools even exist today to measure this empirically — but I remember a big part of the open source community when I was younger being things like Inkscape, Themes.Org and Slashdot, not React, Docker Hub and Hacker News.

This is not to say that the hobbyist world no longer exists. There is of course a ton of stuff going on with Raspberry Pi, Home Assistant, OwnCloud, and so on. If anything there’s a bit of a resurgence of self-hosting. But the interests of self-hosters and corporate developers are growing apart; there seems to be far less of a beneficial overflow from corporate infrastructure projects into these enthusiast or prosumer communities.

This is the concrete call to action: if you are employed in any capacity as an open source maintainer, dedicate more energy to medium- or small-scale open source projects.

If your assumption is that you will eventually reach a hyper-scale inflection point, then mimicking Facebook and Netflix is likely to be a good idea. However, if we can all admit to ourselves that we’re not going to achieve a trillion-dollar valuation and a hundred thousand engineer headcount, we can begin to consider ways to make our Next Thing a bit smaller, and to accommodate the world as it is rather than as we wish it would be.

Be Prepared to Scale Down

Here are some design guidelines you might consider, for just about any open source project, particularly infrastructure ones:

  1. Don’t assume that your software can sustain an arbitrarily large fixed overhead because “you just pay that cost once” and you’re going to be running a billion instances so it will always amortize; maybe you’re only going to be running ten.

  2. Remember that such fixed overhead includes not just CPU, RAM, and filesystem storage, but also the learning curve for developers. Front-loading a massive amount of conceptual complexity to accommodate the problems of hyper-scalers is a common mistake. Try to smooth out these complexities and introduce them only when necessary.

  3. Test your code on edge devices. This means supporting Windows and macOS, and even Android and iOS. If you want your tool to help empower individual users, you will need to meet them where they are, which is not on an EC2 instance.

  4. This includes considering Desktop Linux as a platform, as opposed to Server Linux as a platform, which (while they certainly have plenty in common) they are also distinct in some details. Consider the highly specific example of secret storage: if you are writing something that intends to live in a cloud environment, and you need to configure it with a secret, you will probably want to provide it via a text file or an environment variable. By contrast, if you want this same code to run on a desktop system, your users will expect you to support the Secret Service. This will likely only require a few lines of code to accommodate, but it is a massive difference to the user experience.

  5. Don’t rely on LLMs remaining cheap or free. If you have LLM-related features4, make sure that they are sufficiently severable from the rest of your offering that if ChatGPT starts costing $1000 a month, your tool doesn’t break completely. Similarly, do not require that your users have easy access to half a terabyte of VRAM and a rack full of 5090s in order to run a local model.

Even if you were going to scale up to infinity, the ability to scale down and consider smaller deployments means that you can run more comfortably on, for example, a developer’s laptop. So even if you can’t convince your employer that this is where the economy and the future of technology in our lifetimes is going, it can be easy enough to justify this sort of design shift, particularly as individual choices. Make your onboarding cheaper, your development feedback loops tighter, and your systems generally more resilient to economic headwinds.

So, please design your open source libraries, applications, and services to run on smaller devices, with less complexity. It will be worth your time as well as your users’.

But if you can fix the whole wealth inequality thing, do that first.

Acknowledgments

Thank you to my patrons who are supporting my writing on this blog. If you like what you’ve read here and you’d like to read more of it, or you’d like to support my various open-source endeavors, you can support my work as a sponsor!


  1. These sorts of lists are pretty funny reads, in retrospect. 

  2. Which is to say, “distraction”. 

  3. ... or even their lesser-but-still-profound aftershocks like “Social Media”, “Smartphones”, or “On-Demand Streaming Video” ... secondary manifestations of the underlying innovation of a packet-switched global digital network ... 

  4. My preference would of course be that you just didn’t have such features at all, but perhaps even if you agree with me, you are part of an organization with some mandate to implement LLM stuff. Just try not to wrap the chain of this anchor all the way around your code’s neck. 

January 02, 2026 01:59 AM UTC


Seth Michael Larson

New ROM dumping tool for SNES & Super Famicom from Epilogue

Just heard the news from the WULFF Den Podcast that Epilogue has released pre-orders for the next ROM backup tool in their “Operator” series for the Super NES (SNES) and Super Famicom called the “SN Operator”. The SN Operator pre-order costs $60 USD plus shipping.

This is great news for collectors and people interested in owning and playing SNES and Super Famicom games without a subscription service and for cheaper than purchasing a console, currently hovering around $120 USD on eBay. If there's only one or two games you're interested in playing and ownership isn't a huge deal for you: Nintendo Switch Online offers a substantial SNES and Super Famicom library for $20/year.

Most importantly, these devices are important to provide legal pathways for enthusiasts to archive and play their aging collection on newer storage mediums and devices. Emulation does not equate to piracy, and ROM dumpers are an important tool for legal emulation and archiving. I've previously written about using the GB Operator from Epilogue with Ubuntu to successfully archive my Game Boy, Game Boy Color, and Game Boy Advance ROMs and save files.

I personally won't be buying this product as my collection doesn't have any SNES games. Is there a game in particular that I missed out on from this era? Let me know.

Their website also features a teaser for another upcoming Operator! The Wulff brothers guessed that this would likely be an N64 Operator, especially given the N64's prominence with the new Analogue 3D console. An N64 Operator similarly wouldn't be super useful for me, I am holding out hope for a SEGA Genesis/Mega Drive Operator in the future.



Thanks for keeping RSS alive! ♥

January 02, 2026 12:00 AM UTC

January 01, 2026


Python Morsels

Implicit string concatenation

Python automatically concatenates adjacent string literals thanks to implicit string concatenation. This feature can sometimes lead to bugs.

Table of contents

  1. Strings next to each other
  2. Implicit string concatenation
  3. Implicit line continuations with implicit string concatenation
  4. Concatenating lines of text
  5. Bugs caused by implicit string concatenation
  6. Linter rules for implicit string concatenation
  7. Implicit string concatenation is a bug and a feature

Strings next to each other

Take a look at this line of Python code:

>>> print("Hello" "world!")

It looks kind of like we're passing multiple arguments to the built-in print function.

But we're not:

>>> print("Hello" "world!")
Helloworld!

If we pass multiple arguments to print, Python will put spaces between those values when printing:

>>> print("Hello", "world!")
Hello world!

But Python wasn't doing.

Our code from before didn't have commas to separate the arguments (note the missing comma between "Hello" and "world!"):

>>> print("Hello" "world!")
Helloworld!

How is that possible? This seems like it should have resulted in a SyntaxError!

Implicit string concatenation

A string literal is the …

Read the full article: https://www.pythonmorsels.com/implicit-string-concatenation/

January 01, 2026 05:43 PM UTC


Zero to Mastery

[December 2025] Python Monthly Newsletter 🐍

73rd issue of Andrei's Python Monthly: A big change is coming. Read the full newsletter to get up-to-date with everything you need to know from last month.

January 01, 2026 10:00 AM UTC


Seth Michael Larson

Cutting spritesheets like cookies with Python & Pillow 🍪

Happy new year! 🎉 For an upcoming project on the blog requiring many video-game sprites I've created a small tool (“sugarcookie”) using the always-lovely Python image-processing library Pillow. This tool takes a spritesheet and a list of mask colors, a minimum size, and then cuts the spritesheet into its component sprites.

I'm sure this could be implemented more efficiently, or with a friendly command line interface, but for more own purposes (~10 spritesheets) this worked just fine. Feel free to use, share, and improve. The script is available as a GitHub gist, but also included below.

Source code for sugarcookie

#!/usr/bin/env python
# /// script
# requires-python = ">=3.13"
# dependencies = [
#   "Pillow",
#   "tqdm"
# ]
# ///
# License: MIT
# Copyright 2025, Seth Larson

import os.path
import math
from PIL import Image
import tqdm

# Parameters
spritesheet = ""  # Path to spritesheet.
masks = {}  # Set of 3-tuples for RGB.
min_dim = 10  # Min and max dimensions in pixels.
max_dim = 260

img = Image.open(spritesheet)
if img.mode == "RGB":  # Ensure an alpha channel.
    alpha = Image.new("L", img.size, 255)
    img.putalpha(alpha)

output_prefix = os.path.splitext(os.path.basename(spritesheet))[0]
data = img.getdata()
visited = set()
shapes = set()
reroll_shapes = set()


def getpixel(x, y) -> tuple[int, int, int, int]:
    return data[x + (img.width * y)]


def make_2n(value: int) -> int:
    return 2 ** int(math.ceil(math.log2(value)))


with tqdm.tqdm(
    desc="Cutting cookies",
    total=int(img.width * img.height),
    unit="pixels",
) as t:
    for x in range(img.width):
        for y in range(img.height):
            xy = (x, y)
            if xy in visited:
                continue
            inshape = set()
            candidates = {(x, y)}

            def add_candidates(cx, cy):
                global candidates
                candidates |= {(cx - 1, cy), (cx + 1, cy), (cx, cy - 1), (cx, cy + 1)}

            while candidates:
                cx, cy = candidates.pop()
                if (
                    (cx, cy) in visited
                    or cx < 0
                    or cx >= img.width
                    or cy < 0
                    or cy >= img.height
                    or abs(cx - x) > max_dim
                    or abs(cy - y) > max_dim
                ):
                    continue
                visited.add((cx, cy))
                rgba = r, g, b, a = getpixel(cx, cy)
                if a == 0 or (r, g, b) in masks:
                    continue
                else:
                    inshape.add((cx, cy))
                    add_candidates(cx, cy)
            if inshape:
                shapes.add(tuple(inshape))
        t.update(img.height)

max_width = 0
max_height = 0
shapes_and_offsets = []
for shape in sorted(shapes):
    min_x = img.width + 2
    min_y = img.height + 2
    max_x = -1
    max_y = -1
    for x, y in shape:
        max_x = max(x, max_x)
        max_y = max(y, max_y)
        min_x = min(x, min_x)
        min_y = min(y, min_y)
    width = max_x - min_x + 1
    height = max_y - min_y + 1

    # Too small! We have to reroll this
    # potentially into another shape.
    if width < min_dim or height < min_dim:
        reroll_shapes.add(shape)
        continue

    max_width = max(max_width, width)
    max_height = max(max_height, height)
    shapes_and_offsets.append((shape, (width, height), (min_x, min_y)))

# Make them powers of two!
max_width = make_2n(max_width)
max_height = make_2n(max_height)

sprite_number = 0
with tqdm.tqdm(
    desc="Baking cookies",
    total=len(shapes_and_offsets),
    unit="sprites"
) as t:
    for shape, (width, height), (offset_x, offset_y) in shapes_and_offsets:
        new_img = Image.new(mode="RGBA", size=(max_width, max_height))
        margin_x = (max_width - width) // 2
        margin_y = (max_height - height) // 2
        for rx in range(max_width):
            for ry in range(max_height):
                x = rx + offset_x
                y = ry + offset_y
                if (x, y) not in shape:
                    continue
                new_img.putpixel((rx + margin_x, ry + margin_y), getpixel(x, y))
        new_img.save(f"images/{output_prefix}-{sprite_number}.png")
        sprite_number += 1
        t.update(1)

When using the tool you may find yourself needing to add additional masking across elements, such as the original spritesheet curator's name, in order for the cutting process to work perfectly. This script also doesn't work great for sprites which aren't contiguous across their bounding box. There's an exercise left to the reader to implement reroll_shapes, a feature I didn't end up needing for my own project. Let me know if you implement this and send me a patch!



Thanks for keeping RSS alive! ♥

January 01, 2026 12:00 AM UTC