What’s New In Python 3.12




February 07, 2023

This article explains the new features in Python 3.12, compared to 3.11.

For full details, see the changelog.


Prerelease users should be aware that this document is currently in draft form. It will be updated substantially as Python 3.12 moves towards release, so it’s worth checking back even after reading earlier versions.

Summary – Release highlights

Important deprecations, removals or restrictions:

  • PEP 623, Remove wstr from Unicode

  • PEP 632, Remove the distutils package.

Improved Error Messages

  • Modules from the standard library are now potentially suggested as part of the error messages displayed by the interpreter when a NameError is raised to the top level. Contributed by Pablo Galindo in gh-98254.

    >>> sys.version_info
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'sys' is not defined. Did you forget to import 'sys'?
  • Improve the error suggestion for NameError exceptions for instances. Now if a NameError is raised in a method and the instance has an attribute that’s exactly equal to the name in the exception, the suggestion will include self.<NAME> instead of the closest match in the method scope. Contributed by Pablo Galindo in gh-99139.

    >>> class A:
    ...    def __init__(self):
    ...        self.blech = 1
    ...    def foo(self):
    ...        somethin = blech
    >>> A().foo()
      File "<stdin>", line 1
        somethin = blech
    NameError: name 'blech' is not defined. Did you mean: 'self.blech'?
  • Improve the SyntaxError error message when the user types import x from y instead of from y import x. Contributed by Pablo Galindo in gh-98931.

    >>> import a.y.z from b.y.z
      File "<stdin>", line 1
        import a.y.z from b.y.z
    SyntaxError: Did you mean to use 'from ... import ...' instead?
  • ImportError exceptions raised from failed from <module> import <name> statements now include suggestions for the value of <name> based on the available names in <module>. Contributed by Pablo Galindo in gh-91058.

    >>> from collections import chainmap
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ImportError: cannot import name 'chainmap' from 'collections'. Did you mean: 'ChainMap'?

New Features

Other Language Changes

  • types.MappingProxyType instances are now hashable if the underlying mapping is hashable. (Contributed by Serhiy Storchaka in gh-87995.)

  • memoryview now supports the half-float type (the “e” format code). (Contributed by Dong-hee Na and Antoine Pitrou in gh-90751.)

  • The parser now raises SyntaxError when parsing source code containing null bytes. (Contributed by Pablo Galindo in gh-96670.)

  • ast.parse() now raises SyntaxError instead of ValueError when parsing source code containing null bytes. (Contributed by Pablo Galindo in gh-96670.)

  • The Garbage Collector now runs only on the eval breaker mechanism of the Python bytecode evaluation loop instead of object allocations. The GC can also run when PyErr_CheckSignals() is called so C extensions that need to run for a long time without executing any Python code also have a chance to execute the GC periodically. (Contributed by Pablo Galindo in gh-97922.)

  • A backslash-character pair that is not a valid escape sequence now generates a SyntaxWarning, instead of DeprecationWarning. For example, re.compile("\d+\.\d+") now emits a SyntaxWarning ("\d" is an invalid escape sequence), use raw strings for regular expression: re.compile(r"\d+\.\d+"). In a future Python version, SyntaxError will eventually be raised, instead of SyntaxWarning. (Contributed by Victor Stinner in gh-98401.)

  • Octal escapes with value larger than 0o377 (ex: "\477"), deprecated in Python 3.11, now produce a SyntaxWarning, instead of DeprecationWarning. In a future Python version they will be eventually a SyntaxError. (Contributed by Victor Stinner in gh-98401.)

  • All builtin and extension callables expecting boolean parameters now accept arguments of any type instead of just bool and int. (Contributed by Serhiy Storchaka in gh-60203.)

  • Variables used in the target part of comprehensions that are not stored to can now be used in assignment expressions (:=). For example, in [(b := 1) for a, b.prop in some_iter], the assignment to b is now allowed. Note that assigning to variables stored to in the target part of comprehensions (like a) is still disallowed, as per PEP 572. (Contributed by Nikita Sobolev in gh-100581.)

New Modules

  • None yet.

Improved Modules






  • Pseudo instruction opcodes (which are used by the compiler but do not appear in executable bytecode) are now exposed in the dis module. HAVE_ARGUMENT is still relevant to real opcodes, but it is not useful for pseudo instructions. Use the new hasarg collection instead. (Contributed by Irit Katriel in gh-94216.)






  • shutil.make_archive() now passes the root_dir argument to custom archivers which support it. In this case it no longer temporarily changes the current working directory of the process to root_dir to perform archiving. (Contributed by Serhiy Storchaka in gh-74696.)




  • The Unicode database has been updated to version 15.0.0. (Contributed by Benjamin Peterson in gh-96734).



The tempfile.NamedTemporaryFile function has a new optional parameter delete_on_close (Contributed by Evgeny Zorin in gh-58451.)



  • Removed wstr and wstr_length members from Unicode objects. It reduces object size by 8 or 16 bytes on 64bit platform. (PEP 623) (Contributed by Inada Naoki in gh-92536.)

  • Added experimental support for using the BOLT binary optimizer in the build process, which improves performance by 1-5%. (Contributed by Kevin Modzelewski in gh-90536.)

  • Speed up the regular expression substitution (functions re.sub() and re.subn() and corresponding re.Pattern methods) for replacement strings containing group references by 2–3 times. (Contributed by Serhiy Storchaka in gh-91524.)

CPython bytecode changes

Demos and Tools

  • Remove the Tools/demo/ directory which contained old demo scripts. A copy can be found in the old-demos project. (Contributed by Victor Stinner in gh-97681.)

  • Remove outdated example scripts of the Tools/scripts/ directory. A copy can be found in the old-demos project. (Contributed by Victor Stinner in gh-97669.)


Pending Removal in Python 3.13

The following modules and APIs have been deprecated in earlier Python releases, and will be removed in Python 3.13.

Modules (see PEP 594):


Pending Removal in Python 3.14

Pending Removal in Future Versions

The following APIs were deprecated in earlier Python versions and will be removed, although there is currently no date scheduled for their removal.

  • typing.Text (gh-92332)

  • Currently Python accepts numeric literals immediately followed by keywords, for example 0in x, 1or x, 0if 1else 2. It allows confusing and ambiguous expressions like [0x1for x in y] (which can be interpreted as [0x1 for x in y] or [0x1f or x in y]). A syntax warning is raised if the numeric literal is immediately followed by one of keywords and, else, for, if, in, is and or. In a future release it will be changed to a syntax error. (gh-87999)


  • asynchat and asyncore have been removed according to the schedule in PEP 594, having been deprecated in Python 3.6. Use asyncio instead. (Contributed by Nikita Sobolev in gh-96580.)

  • Remove io.OpenWrapper and _pyio.OpenWrapper, deprecated in Python 3.10: just use open() instead. The open() (io.open()) function is a built-in function. Since Python 3.10, _pyio.open() is also a static method. (Contributed by Victor Stinner in gh-94169.)

  • Remove the ssl.RAND_pseudo_bytes() function, deprecated in Python 3.6: use os.urandom() or ssl.RAND_bytes() instead. (Contributed by Victor Stinner in gh-94199.)

  • gzip: Remove the filename attribute of gzip.GzipFile, deprecated since Python 2.6, use the name attribute instead. In write mode, the filename attribute added '.gz' file extension if it was not present. (Contributed by Victor Stinner in gh-94196.)

  • Remove the ssl.match_hostname() function. The ssl.match_hostname() was deprecated in Python 3.7. OpenSSL performs hostname matching since Python 3.7, Python no longer uses the ssl.match_hostname() function. (Contributed by Victor Stinner in gh-94199.)

  • Remove the locale.format() function, deprecated in Python 3.7: use locale.format_string() instead. (Contributed by Victor Stinner in gh-94226.)

  • hashlib: Remove the pure Python implementation of hashlib.pbkdf2_hmac(), deprecated in Python 3.10. Python 3.10 and newer requires OpenSSL 1.1.1 (PEP 644): this OpenSSL version provides a C implementation of pbkdf2_hmac() which is faster. (Contributed by Victor Stinner in gh-94199.)

  • xml.etree: Remove the ElementTree.Element.copy() method of the pure Python implementation, deprecated in Python 3.10, use the copy.copy() function instead. The C implementation of xml.etree has no copy() method, only a __copy__() method. (Contributed by Victor Stinner in gh-94383.)

  • zipimport: Remove find_loader() and find_module() methods, deprecated in Python 3.10: use the find_spec() method instead. See PEP 451 for the rationale. (Contributed by Victor Stinner in gh-94379.)

  • Remove the ssl.wrap_socket() function, deprecated in Python 3.7: instead, create a ssl.SSLContext object and call its ssl.SSLContext.wrap_socket method. Any package that still uses ssl.wrap_socket() is broken and insecure. The function neither sends a SNI TLS extension nor validates server hostname. Code is subject to CWE-295: Improper Certificate Validation. (Contributed by Victor Stinner in gh-94199.)

  • Many previously deprecated cleanups in importlib have now been completed:

    • References to, and support for module_repr() has been eradicated.

  • importlib.util.set_package has been removed. (Contributed by Brett Cannon in gh-65961.)

  • Removed the suspicious rule from the documentation Makefile, and removed Doc/tools/rstlint.py, both in favor of sphinx-lint. (Contributed by Julien Palard in gh-98179.)

  • Remove the keyfile and certfile parameters from the ftplib, imaplib, poplib and smtplib modules, and the key_file, cert_file and check_hostname parameters from the http.client module, all deprecated since Python 3.6. Use the context parameter (ssl_context in imaplib) instead. (Contributed by Victor Stinner in gh-94172.)

  • ftplib: Remove the FTP_TLS.ssl_version class attribute: use the context parameter instead. (Contributed by Victor Stinner in gh-94172.)

Porting to Python 3.12

This section lists previously described changes and other bugfixes that may require changes to your code.

Changes in the Python API

  • More strict rules are now applied for numerical group references and group names in regular expressions. Only sequence of ASCII digits is now accepted as a numerical reference. The group name in bytes patterns and replacement strings can now only contain ASCII letters and digits and underscore. (Contributed by Serhiy Storchaka in gh-91760.)

  • Removed randrange() functionality deprecated since Python 3.10. Formerly, randrange(10.0) losslessly converted to randrange(10). Now, it raises a TypeError. Also, the exception raised for non-integral values such as randrange(10.5) or randrange('10') has been changed from ValueError to TypeError. This also prevents bugs where randrange(1e25) would silently select from a larger range than randrange(10**25). (Originally suggested by Serhiy Storchaka gh-86388.)

  • argparse.ArgumentParser changed encoding and error handler for reading arguments from file (e.g. fromfile_prefix_chars option) from default text encoding (e.g. locale.getpreferredencoding(False)) to filesystem encoding and error handler. Argument files should be encoded in UTF-8 instead of ANSI Codepage on Windows.

  • Removed the asyncore-based smtpd module deprecated in Python 3.4.7 and 3.5.4. A recommended replacement is the asyncio-based aiosmtpd PyPI module.

  • shlex.split(): Passing None for s argument now raises an exception, rather than reading sys.stdin. The feature was deprecated in Python 3.9. (Contributed by Victor Stinner in gh-94352.)

  • The os module no longer accepts bytes-like paths, like bytearray and memoryview types: only the exact bytes type is accepted for bytes strings. (Contributed by Victor Stinner in gh-98393.)

  • syslog.openlog() and syslog.closelog() now fail if used in subinterpreters. syslog.syslog() may still be used in subinterpreters, but now only if syslog.openlog() has already been called in the main interpreter. These new restrictions do not apply to the main interpreter, so only a very small set of users might be affected. This change helps with interpreter isolation. Furthermore, syslog is a wrapper around process-global resources, which are best managed from the main interpreter. (Contributed by Dong-hee Na in gh-99127.)

Build Changes

  • Python no longer uses setup.py to build shared C extension modules. Build parameters like headers and libraries are detected in configure script. Extensions are built by Makefile. Most extensions use pkg-config and fall back to manual detection. (Contributed by Christian Heimes in gh-93939.)

  • va_start() with two parameters, like va_start(args, format), is now required to build Python. va_start() is no longer called with a single parameter. (Contributed by Kumar Aditya in gh-93207.)

  • CPython now uses the ThinLTO option as the default link time optimization policy if the Clang compiler accepts the flag. (Contributed by Dong-hee Na in gh-89536.)

  • Add COMPILEALL_OPTS variable in Makefile to override compileall options (default: -j0) in make install. Also merged the 3 compileall commands into a single command to build .pyc files for all optimization levels (0, 1, 2) at once. (Contributed by Victor Stinner in gh-99289.)

C API Changes

New Features

Porting to Python 3.12

  • Legacy Unicode APIs based on Py_UNICODE* representation has been removed. Please migrate to APIs based on UTF-8 or wchar_t*.

  • Argument parsing functions like PyArg_ParseTuple() doesn’t support Py_UNICODE* based format (e.g. u, Z) anymore. Please migrate to other formats for Unicode like s, z, es, and U.

  • tp_weaklist for all static builtin types is always NULL. This is an internal-only field on PyTypeObject but we’re pointing out the change in case someone happens to be accessing the field directly anyway. To avoid breakage, consider using the existing public C-API instead, or, if necessary, the (internal-only) _PyObject_GET_WEAKREFS_LISTPTR() macro.

  • This internal-only PyTypeObject.tp_subclasses may now not be a valid object pointer. Its type was changed to void* to reflect this. We mention this in case someone happens to be accessing the internal-only field directly.

    To get a list of subclasses, call the Python method __subclasses__() (using PyObject_CallMethod(), for example).

  • An unrecognized format character in PyUnicode_FromFormat() and PyUnicode_FromFormatV() now sets a SystemError. In previous versions it caused all the rest of the format string to be copied as-is to the result string, and any extra arguments discarded. (Contributed by Serhiy Storchaka in gh-95781.)

  • Fixed wrong sign placement in PyUnicode_FromFormat() and PyUnicode_FromFormatV(). (Contributed by Philip Georgi in gh-95504.)

  • Extension classes wanting to add a __dict__ or weak reference slot should use Py_TPFLAGS_MANAGED_DICT and Py_TPFLAGS_MANAGED_WEAKREF instead of tp_dictoffset and tp_weaklistoffset, respectively. The use of tp_dictoffset and tp_weaklistoffset is still supported, but does not fully support multiple inheritance (gh-95589), and performance may be worse. Classes declaring Py_TPFLAGS_MANAGED_DICT should call _PyObject_VisitManagedDict() and _PyObject_ClearManagedDict() to traverse and clear their instance’s dictionaries. To clear weakrefs, call PyObject_ClearWeakRefs(), as before.

  • The PyUnicode_FSDecoder() function no longer accepts bytes-like paths, like bytearray and memoryview types: only the exact bytes type is accepted for bytes strings. (Contributed by Victor Stinner in gh-98393.)

  • The Py_CLEAR, Py_SETREF and Py_XSETREF macros now only evaluate their arguments once. If an argument has side effects, these side effects are no longer duplicated. (Contributed by Victor Stinner in gh-98724.)



  • Remove the token.h header file. There was never any public tokenizer C API. The token.h header file was only designed to be used by Python internals. (Contributed by Victor Stinner in gh-92651.)

  • Legacy Unicode APIs have been removed. See PEP 623 for detail.

    • PyUnicode_WCHAR_KIND

    • PyUnicode_AS_UNICODE()

    • PyUnicode_AsUnicode()

    • PyUnicode_AsUnicodeAndSize()

    • PyUnicode_AS_DATA()

    • PyUnicode_FromUnicode()

    • PyUnicode_GET_SIZE()

    • PyUnicode_GetSize()

    • PyUnicode_GET_DATA_SIZE()

  • Remove the PyUnicode_InternImmortal() function and the SSTATE_INTERNED_IMMORTAL macro. (Contributed by Victor Stinner in gh-85858.)

  • Remove Jython compatibility hacks from several stdlib modules and tests. (Contributed by Nikita Sobolev in gh-99482.)

  • Remove _use_broken_old_ctypes_structure_semantics_ flag from ctypes module. (Contributed by Nikita Sobolev in gh-99285.)