Python Monthly News

Python News: What's New From September 2023

by Bartosz Zaczyński Oct 09, 2023 community

As the leaves turned yellow and fall set in, the Python community prepared for the release of Python 3.12 in early October. This new version of the most popular programming language brings several exciting new features, including better error messages and improvements to static typing. It also lays the groundwork for enhanced parallelism.

Microsoft Excel is about to become a friendlier space for Pythonistas with the integration of the Python Editor, opening up new possibilities for those bridging the gap between coding and data analysis.

September also saw the release of the Mojo Software Development Kit (SDK) for Linux by Modular. As a superset of Python, Mojo has been the talk of the town, offering features such as familiar syntax, Python interoperability, and unrivaled performance.

Web developers weren’t left behind, with the availability of Django 5.0 alpha 1 revealing some promising enhancements. The Django community also got a chance to express their thoughts through the Django Developers Survey 2023.

Part of what makes Python’s growth successful is the amount of thought that goes into every change. This thoughful approach was on display in the rejection of PEP 713, the proposal to include callable modules in Python 3.12.

Let’s plunge into the most exciting Python news from September 2023!

Python 3.12 Arrives in October 2023

Although this article covers the major events of September, you’re reading it at a time when Python 3.12 has already been released. Because it’s such a big and long-awaited event in the Python community, we’ve decided to include it in this roundup.

Every year in early October, a new version of Python gets released, and 2023 was no exception. Python 3.12 arrived with a range of features and improvements. While they predominantly enhance the interpreter’s internal workings, the new release still brings a few tangible benefits to the table.

The key new features in Python 3.12 that most developers will be excited about include:

In addition to these, you can expect to see various performance optimizations and several improvements to standard library modules, as well as many deprecations and removals that have been planned for a long time.

If you’re interested in reading a more detailed summary of the changes, then check out What’s New In Python 3.12 and the official Python 3.12 changelog.

You can also stay right here on the Real Python website and immerse yourself in the ultimate Python 3.12 experience by diving into one or all of the following resources:

Want more? Consider joining the discussion on the Real Python Community chat, where you can ask questions, share insights, and connect with other Python enthusiasts eager to learn about Python 3.12. You can also learn from those who have already been using the Python 3.12 pre-release versions.

If you prefer a live Q&A session with screen sharing, then we invite you to drop by a virtual Office Hours session hosted weekly by our team members. It’s a great opportunity to hang out with experts from the Python community and fellow Pythonistas like you. While there’s no fixed agenda, you can always ask questions specific to Python 3.12 or remain a silent participant by simply observing and learning from the discussions.

As with every new release, upgrading to the latest Python version right away might not be feasible or desirable, especially for businesses where stability and backward compatibility are crucial. It usually takes time for library vendors to catch up. That said, a few notable libraries, including NumPy and pandas, already rolled out Python wheels targeting Python 3.12 on PyPI back in September.

Are you ready to take the leap into Python 3.12 yet?

Microsoft Excel Gets the Python Editor

Following the recent news from Microsoft and Anaconda about their partnership to bring Python into Excel, they don’t seem to be slowing down. Just this month, the Excel team at Microsoft announced the introduction of an experimental code editor for Python. While it’s only available as an extension at the moment, the editor may eventually become an integral part of the popular spreadsheet software.

Today, you can enable the Python Editor by installing the Excel Labs add-in, which is a Microsoft Garage project that started earlier this year to collect user feedback about experimental features in Excel. Primarily, this add-in provides an advanced formula-editing interface featuring syntax highlighting, auto-completion, inline error reporting, and more. Now, it also comes with a dedicated Python editor:

Python Editor in Microsoft Excel
Image source

Thanks to being built on top of the open-source Monaco Editor, which is also a core component of the familiar Visual Studio Code, the Python Editor can run in your web browser. That’s essential for the ability to use the editor in the cloud-based Microsoft 365 office suite.

Anyone who tried the Python and Excel integration released less than a month earlier will immediately appreciate several advantages that the new Python Editor delivers:

  • Linear execution flow: The editor was designed to mimic the experience of working with Jupyter Notebooks due to their immense popularity among the data science community. Python in Excel seems to mainly target data scientists, so this choice makes sense. Like a typical notebook, the Python Editor shows individual spreadsheet cells with Python source code organized as a sequence that follows the row-major calculation order of grid cells in Excel.

  • Centralized navigation: The editor gathers all the code snippets from all over the grid and shows them in one accessible place, making navigation through cells with Python source code more convenient. You can find your entire Python code in a sidebar on the right, which also helps you see the bigger picture.

  • Output preview: Each cell in the Python Editor contains the output from the corresponding code snippet, which can help speed up the debugging process. That said, the preview was unable to render some types of content, such as plots, at the time of writing.

  • Spacious workspace: Having additional space for code makes editing long and non-trivial Python fragments more straightforward than using the narrow formula bar or the standard grid cell.

  • Convenient editing: The Python Editor boasts advanced features typically found in full-blown code editors or IDEs, including syntax highlighting, auto-completion, code formatting, and others.

Whether you’ve enabled Python in Excel or not, you can install the Python Editor. Unfortunately, to take full advantage of the editor, you must also be eligible to use Python in Excel, which requires becoming a member of the Microsoft 365 Insider program’s Beta Channel. After signing up, you’ll join a waiting list, which might take some time to process, so be patient.

What do you think about Microsoft adding a native Python development experience to Excel? Will it improve your current workflows?

Modular Releases Mojo SDK for Linux

In early September, just four months after the initial announcement of a new programming language, Modular shared on their blog that the Mojo compiler had become available for local download.

Along with the Mojo SDK, they released a Mojo extension for Visual Studio Code, which adds syntax highlighting, code completion, code formatting, API documentation, and the ability to run Mojo files directly from the editor:

Visual Studio Code Extension for Mojo
Visual Studio Code Extension for Mojo

Additionally, the installed package comes with a Mojo kernel for Jupyter Notebooks, which the VS Code extension can detect and use.

Modular: The Company Behind Mojo

Founded just last year, Modular is a promising startup company whose mission is to increase the pace of innovation in the field of artificial intelligence (AI):

Our mission is to have real, positive impact in the world by reinventing the way AI technology is developed and deployed into production with a next-generation developer platform. (Source)

The current AI platforms suffer from fragmented infrastructure with a high degree of complexity, making the deployment of machine learning workloads difficult. The company aims to address these challenges by offering a single platform to handle models from various AI frameworks.

The CEO and one of the co-founders of Modular is none other than Chris Lattner, renowned for inventing the LLVM compiler over two decades ago and, more recently, the Swift programming language during his time at Apple. Now, he’s using his expertise in compiler design to build Mojo, a brand-new programming language based on Python, which can leverage the heterogeneous multi-core hardware architectures of modern computers to make performance gains.

Mojo: A Superset of Python

Because Python powers the overwhelming majority of AI models, Mojo was designed as a Python superset to be compatible with existing machine learning libraries, frameworks, and codebases. This lowers the entry barrier while making developers more productive. At the same time, Mojo combines the elegance of the familiar Python syntax with the speed of compiled languages like C and C++. In some cases, it can run up to 68,000 times faster than Python!

During their product launch keynote earlier this year, Modular sparked quite a lot of excitement in the Python and data science communities by revealing Mojo to the world. It was initially only available through a limited preview in an online playground. Four months later, the Mojo compiler is available for download, making offline development possible.

Today, anyone can sign up for a Modular account and download the Software Development Kit (SDK) for Mojo, which includes a compiler for Linux.

Mojo Compiler

While the Mojo SDK is only available for Ubuntu Linux at the moment, other platforms will gain native support in future releases. In the meantime, Modular recommends running the SDK on macOS and Windows through a Docker container, a remote virtual machine in the cloud, or GitHub Codespaces.

Clone the modularml/mojo repository from GitHub and build the provided Docker image:

$ git clone
$ cd mojo/examples/docker/
$ chmod +x
$ ./ --auth-key <YOUR_AUTH_TOKEN>

You’ll find the required authorization token when you sign in to your Modular account and navigate to the Mojo download page. On the right-hand side of that page, you’ll find setup instructions. From there, you can copy the value of the MODULAR_AUTH environment variable, which is your unique secret token.

After a few minutes, you’ll be able to run the Mojo REPL in an interactive Docker container or run JupyterLab with the Jupyter kernel for Mojo in the background:

$ docker images
REPOSITORY                        TAG       IMAGE ID       ...   SIZE
modular/mojo-v0.3-20232809-1947   latest    1c729f7db052   ...   2.58GB

$ docker run --rm -it modular/mojo-v0.3-20232809-1947 mojo
Welcome to Mojo! 🔥

Expressions are delimited by a blank line.
Type `:quit` to exit the REPL and `:mojo help` for further assistance.


$ docker run --rm -p 8888:8888 -v $PWD:/shared modular/mojo-v0.3-20232809-1947
$ open http://localhost:8888/lab

If you have the Docker extension, then you can hook up your Visual Studio Code to a running Docker container with the Mojo SDK for a seamless developer experience.

The first step to getting the Mojo SDK onto a local machine is to download and install the Modular CLI, which provides the modular command in the terminal. Among other things, it allows you to install or update the Mojo SDK like so:

$ modular install mojo
# Found release for @ 0.3.1
# Installing to /home/realpython/.modular/pkg/packages.modular.com_mojo
# Downloading artifacts. Please wait...
# Downloads complete, setting configs...
# Configs complete, running post-install hooks...


🔥 Mojo installed! 🔥

$ modular update mojo
# No updates available for this target.

Now you have the Mojo CLI, comprising a single mojo command. To confirm that you’ve installed the Mojo SDK correctly, you can check the current Mojo version:

$ mojo --version
mojo 0.3.1 (a3eed7c8)

The mojo command comes with subcommands that integrate several tools, including a code formatter, documentation generator, compiler, and a REPL (Read-Eval-Print Loop) for Mojo.

Mojo Interpreter

The bare mojo command is equivalent to typing mojo repl at the command prompt, which launches the Mojo REPL—an interactive shell for Mojo, resembling the Python REPL:

$ mojo
Welcome to Mojo! 🔥

Expressions are delimited by a blank line.
Type `:quit` to exit the REPL and `:mojo help` for further assistance.

  1> print(
  2.   "Hello, World!"
  3. )
  4. print("Bye!")
Hello, World!

Unlike the standard Python REPL, the Mojo REPL allows you to type multiline expressions or even several expressions at a time, and it expects a blank line to indicate where they end. Perhaps the developers decided to include such a behavior because Mojo compiles the code in the background, which takes extra time. Without this behavior, you might perceive the experience of running code in the Mojo REPL as slow.

Mojo Source Files

While the Mojo REPL allows you to execute code at the top level, Mojo source files don’t. Expressions are only allowed inside nested scopes, and programs must contain the main() function as an entry point unless they’re utility modules:

# hello.mojo

fn main():
    print("Hello, World!")

This is typical of compiled programming languages like C, C++, or Java, which also require an entry point. Note that the print() function in the code snippet above isn’t the same as the print() function in Python! In particular, these functions take different parameters.

To execute a Mojo source file, you can run it directly with the mojo command or the more explicit mojo run subcommand:

$ mojo hello.mojo
Hello, World!

$ mojo run hello.mojo
Hello, World!

In both cases, the source file gets compiled into machine code behind the scenes and discarded immediately afterward. Building a reusable binary file boils down to typing mojo build followed by the Mojo source file:

$ mojo build hello.mojo

$ ./hello
Hello, World!

$ du -h hello
28K hello

$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
⮑ dynamically linked, interpreter /lib64/,
⮑ BuildID[sha1]=ca7b91bfd99556679d79a67852d684ccb3cba4c2,
⮑ for GNU/Linux 3.2.0, not stripped

This generates a binary executable, which is relatively small when compared to executables generated by, say, Go. At the same time, the binary file produced by the Mojo compiler enjoys broad portability across Linux distributions that meet the minimum kernel version and target the same CPU architecture. As a result, Mojo programs are much more straightforward to distribute than Python programs.

Mojo and Python Interoperability

Mojo is currently in its infancy, making it a work in progress, so it’s far from being a complete superset of Python. That said, there are ways to call out existing Python code and combine it with Mojo.

Either in the Mojo REPL or a notebook’s cell that uses the Mojo kernel for Jupyter, you can use the %%python cell magic to interpret Python code:

$ mojo
Welcome to Mojo! 🔥

Expressions are delimited by a blank line.
Type `:quit` to exit the REPL and `:mojo help` for further assistance.

  1> %%python
  2. import sys
  3. print(sys.version)
3.11.5 (main, Aug 30 2023, 17:24:45) [GCC 12.2.0]
(PythonObject) sys = {
  (PyObjectPtr) py_object = {
    (pointer<scalar<si8>>) value = 0x00007f4a9557aca0

Mojo runs Python from a special virtual environment created at installation time. Currently, the installer picks one of the Python interpreters found on the machine without asking the user.

To run Python in a Mojo source file, you need to import the Python interface:

# hello.mojo

from python import Python

fn main() raises:
    let sys = Python.import_module("sys")

Later, it’s possible to access the values computed by Python in the subsequent Mojo code.

Check the official Mojo documentation to learn more about Python interoperability, the Mojo standard library, and more. You can also take a look at a few code examples in the official Mojo repository on GitHub.

Do you share the excitement around Mojo, or do you think it’s just temporary hype? Is Mojo going to replace Python? Tell us in the comments below!

Django 5.0 Alpha 1 Becomes Available

The first alpha release of Django 5.0 is now available, paving the way for the scheduled final release in December this year. Today, you can get your hands dirty with the pre-release version of Django by issuing the following command in the terminal:

(venv) $ python -m pip install django==5.0a1

The biggest changes in Django 5.0 include dropping support for Python 3.8 and Python 3.9, and adding support for Python 3.12 going forward. This means that Django developers may need to upgrade their Python versions before making the switch.

But there’s much more to explore in Django 5.0!

Database-Computed Defaults

An interesting feature introduced in Django 5.0 is the ability to compute default model field values directly at the database level rather than through Django:


from django.db import models
from django.db.models.functions import Now
from django.utils import timezone

class BlogPost(models.Model):
    title = models.CharField(max_length=60)
    created_by_django = models.DateTimeField(
    created_by_database = models.DateTimeField(db_default=Now())

Previously, you could use the default parameter in model fields to declare the default value in case one wasn’t provided at the time of object creation. Django would use that value at runtime before sending a query to the database. In contrast, the new db_default parameter alters the database schema generated by Django:

sqlite> .schema myapp_blogpost
CREATE TABLE IF NOT EXISTS "myapp_blogpost" (
    "created_by_django" datetime NOT NULL,
    "created_by_database" datetime DEFAULT (
        STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')
    ) NOT NULL

The table column named created_by_database in the example above specifies the DEFAULT constraint, unlike the previous column, which doesn’t. The extra constraint reflects the expression assigned to the db_default parameter in Python. Starting from Django 5.0, the web framework allows for the use of database functions, such as Now(), in such expressions.

Not only is this quicker to compute, but it also leads to better data consistency because there’s only one place with a complete set of constraints. Previously, it was possible to insert rows into the table while bypassing the model constraints defined in Django—for example, by executing a script.

Virtual and Persisted Columns

Another new feature related to databases that will make its way to Django 5.0 is the ability to define virtual columns through the web framework:


from django.db import models
from django.db.models import F
from django.db.models.functions import Concat

class User(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    full_name = models.GeneratedField(
        expression=Concat(F("first_name"), " ", F("last_name")),

The new GeneratedField type defines a table column generated by the database, provided that the underlying database engine has such a capability. In this case, the database will compute the value of the full_name field from other fields.

When the db_persist parameter is set to False, the corresponding value will be computed on the fly without taking extra space on disk. On the other hand, querying the database will take longer due to the additional computation that’s necessary with each query.

Sometimes, it’s more desirable to compute the column’s value based on other columns when updating or inserting a new row into the table. Unlike virtual columns, a persistent column is stored on disk, consuming additional space. With Django 5.0, you can set the db_persist parameter’s value to True in order to define such a stored column:


from django.db import models
from django.db.models import F
from django.db.models.functions import Concat

class User(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    full_name = models.GeneratedField(
        expression=Concat(F("first_name"), " ", F("last_name")),

As a result, selecting rows from the corresponding table will be as quick as if the generated column weren’t managed by the database. At the same time, you won’t be able to update virtual columns by hand.

Improved Model Field Choices

Continuing with the theme of Django models, the new version of the popular web framework will accept a few additional data types as the choices parameter of model fields. Traditionally, specifying a limited number of options to choose from in a Django model required defining a sequence of pairs:


from django.db import models

class Person(models.Model):
        ("M", "Minor"),
        ("A", "Adult"),
    age = models.CharField(max_length=1, choices=AGE_CHOICES)

That wasn’t particularly intuitive. Remembering which of the two values corresponds to a human-readable representation or the actual value stored in the database wasn’t always straightforward. Moreover, translating between the two could sometimes get clunky.

Django 5.0 will allow the use of a Python dictionary or any other mapping container type in addition to the classic iterable of tuples:


from django.db import models

class Person(models.Model):
        "M": "Minor",
        "A": "Adult",
    age = models.CharField(max_length=1, choices=AGE_CHOICES)

The advantage of using a dictionary is that it simplifies the mapping of the value retrieved from the database into a descriptive label.

Another new type allowed in this context will be a callable object, such as a function:


from django.db import models

def age_choices(age_types=("Minor", "Adult")):
    return {name[0]: name for name in age_types}

class Person(models.Model):
    age = models.CharField(max_length=1, choices=age_choices)

Calling a function to determine the choices may be suitable when the fixed set of values depends on some inputs supplied at runtime. Alternatively, it may be convenient to compute the choices dynamically—for example, by processing the contents of a large file.

Finally, you won’t need to access the .choices attribute of an enumeration type anymore starting from Django 5.0:


from django.db import models

class Person(models.Model):
    class Age(models.TextChoices):
        MINOR = "M", "Minor"
        ADULT = "A", "Adult"
    age = models.CharField(max_length=1, choices=Age)

Previously, you would’ve had have to supply Age.choices instead of just Age in the last line above.

Reusable Form Field Groups

Django templates will gain a new way of rendering groups of elements related to a form field. Rather than laying out the individual components like the field label, HTML widget, help text, and error messages separately, you can now leverage the .as_field_group property:

<form action="{% url 'form_view' %}" method="post">
  {% csrf_token %}
  {{ }}
  {{ form.value.as_field_group }}
  <input type="submit" value="Send">

This reduces the boilerplate code in Django templates and makes the form-rendering markup more reusable. At the same time, it’ll be possible to tweak the default field group template to customize it if necessary.

These are the most significant features coming in Django 5.0. The release will also include several minor improvements, backward-incompatible changes, deprecations, and removals. To find out more details, read the official release notes on the framework’s website.

Are you excited to try out Django 5.0 in your projects?

Django Developers Survey 2023 Opens

The annual Django Developers Survey for 2023 was open in September. As in previous years, this survey was a collaboration between the Django Software Foundation (DSF) and JetBrains, the company behind PyCharm. Django’s announcement noted that the survey would be open until October 1, with results published afterward.

The goal of the survey is to measure how web developers worldwide use Django and its ecosystem. This information will help shape the technological future of the framework and its community, guiding the direction of the development efforts.

Filling out surveys is a great way to contribute to the Python ecosystem. You can make your mark in about ten minutes, and often the questions are quite interesting and wide-ranging, with topics such as your professional role, employment status, and experience. Other questions on this survey included Python-related practices, cloud services, web frameworks, development tools, and more.

You should take advantage of surveys like this to share your thoughts and experiences with maintainers of your favorite libraries. Your input can make a real difference! Additionally, you can often win prizes. This survey offered a chance to win a $100 Amazon gift card or a local equivalent.

PEP 713: Callable Modules Gets Rejected

The Python Steering Council has decided to reject the proposal for callable modules described in PEP 713, which was planned for inclusion in Python 3.12. The committee felt that there was no compelling reason to implement this new functionality right now. At the same time, they indicated that when a similar idea resurfaces in the future, they might give it another thought. If that happens, then it’s worth knowing some context.

The original proposal addressed a common pattern of giving a callable object the same name as the containing module where it was defined. There are plenty of such examples in Python’s standard library:

from array import array
from bisect import bisect
from calendar import calendar
from copy import copy
from crypt import crypt
from datetime import datetime
from dis import dis
from fcntl import fcntl
from fnmatch import fnmatch
from getopt import getopt
from getpass import getpass
from gettext import gettext
from glob import glob
from mmap import mmap
from netrc import netrc
from platform import platform
from pprint import pprint
from random import random
from select import select
from shlex import shlex
from signal import signal
from socket import socket
from symtable import symtable
from syslog import syslog
from time import time
from timeit import timeit
from tokenize import tokenize

In some cases, the module defines only one public function that’s intended to be called, leading to an awkward-looking way of importing that function.

The rejected document invented a way to make Python modules callable by implementing a special function named __call__() inside that module. As a result of that, external code importing a callable module could invoke it directly, implicitly passing execution to the special function:

>>> import time
>>> time()

>>> import random
>>> random()

This code looks cleaner but could also potentially lead to confusion. The underlying idea leverages an existing mechanism for defining callable classes in Python. It’s also similar to how customization of module attributes was added in Python 3.7.

Do you like the suggested feature? What do you think about the decision to reject the corresponding PEP document?

What’s Next for Python?

September 2023 was an electrifying month for the Python community, with October already starting on a high note. Python 3.12 landed in early October, marking an important milestone that we couldn’t help but share with you at this time.

With such an exciting moment in the Python ecosystem, we’re eager to see what comes next. Let us know your thoughts on these developments in the comments below!

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Bartosz Zaczyński

Bartosz is a bootcamp instructor, author, and polyglot programmer in love with Python. He helps his students get into software engineering by sharing over a decade of commercial experience in the IT industry.

» More about Bartosz

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.

Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Topics: community