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!
Join Now: Click here to join the Real Python Newsletter and you'll never miss another Python tutorial, course update, or post.
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:
- Ever better error messages
- More intuitive and consistent f-strings
- Static typing improvements
- Subinterpreters with isolated GILs
- Support for the Linux
perf
profiler
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:
- Tutorial: Learn about the cool new features in Python 3.12 through hands-on examples that you can follow in a step-by-step fashion.
- Video Course: Enjoy the companion video course that explores the new features in Python 3.12 in a more interactive and visual way.
- Podcast: Grab your favorite beverage, make yourself comfortable, and listen to our industry experts talk about the exciting updates and enhancements in Python 3.12.
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:
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:
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.
Note: Mojo is currently a proprietary technology used internally at Modular with the intention of becoming open source by the end of the year. While it focuses on AI, it may eventually evolve into a general-purpose programming language with wider applications.
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 git@github.com:modularml/mojo.git
$ cd mojo/examples/docker/
$ chmod +x build-image.sh
$ ./build-image.sh --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.
1>
$ 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 https://packages.modular.com/mojo @ 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!")
5.
Hello, World!
Bye!
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.
Note: Mojo files can use either the .mojo
extension or the fire emoji (🔥) as an extension.
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/ld-linux-x86-64.so.2,
⮑ 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)
4.
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")
print(sys.version)
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:
# models.py
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(default=timezone.now)
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" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"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:
# models.py
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")),
db_persist=False
)
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.
Note: Most databases require that the expression that you use to declare a generated column relies on deterministic functions only. The Concat()
function in the example above is indeed deterministic because its result doesn’t change as long as the input remains the same.
In contrast, you wouldn’t be able to call Now()
to calculate a person’s age based on the birth date column and the current date, which keeps changing.
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:
# models.py
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")),
db_persist=True
)
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:
# models.py
from django.db import models
class Person(models.Model):
AGE_CHOICES = [
("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:
# models.py
from django.db import models
class Person(models.Model):
AGE_CHOICES = {
"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:
# models.py
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:
# models.py
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.name.as_field_group }}
{{ form.value.as_field_group }}
<input type="submit" value="Send">
</form>
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()
1696255559.0873122
>>> import random
>>> random()
0.8149008155846644
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!
Join Now: Click here to join the Real Python Newsletter and you'll never miss another Python tutorial, course update, or post.