Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions peps/pep-0812.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
PEP: 812
Title: Immutable variables with const keyword
Author: Michael Voznesensky <[email protected]>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 31-Oct-2025
Python-Version: 3.15
Post-History: Pending
Discussions-To: Pending

Abstract
========

Today, Python variables are wonderfully mutable - this is a super power of the language. However, in larger codebases, or more complex implementations, there is often a need to mark a variable as immutable. This is useful in two major ways. First, general to programming and not specific to Python, assurances of objects staying identical for their lifetimes is a powerful hint to both the programmer and the compiler. Potential compiler optimizations aside, ``const`` hints signal to programmers that the original author of the code intended this object not to change, which, like C++'s ``const`` correctness ideas, promotes safety and readability.

Second, a far more specific Python "Gotcha" is mutable defaults. This PEP proposes a ``const`` keyword that can be inserted in function arguments, defaults, class attributes, and scopes to declare an object as immutable, forbidding both rebinding and mutation.

Motivation
==========

Consider the following code::

def add_item_to_cart(item, cart=[]):
"""
Adds an item to a user's cart.
If no cart is provided, starts a new one.
"""
cart.append(item)
return cart

In this standard example, ``cart`` is evaluated *once* when the function is defined. This means that a second caller appending to the cart is going to see the first item, and so forth—a common mistake.

Another example involves accidentally mutating data that should be a snapshot::

def analyze_latest_scores(current_scores):
original_order = current_scores
current_scores.sort(reverse=True)
return {
"top_score": current_scores[0],
"first_entry": original_order[0]
}

It looks like we are saving a snapshot of the data as it came in, but ``.sort()`` modifies the list *in-place*. Because ``original_order`` is just a reference to ``current_scores``, the returned "first_entry" field will be the top score, not the first entry.

Beyond these edge cases of mutability, a ``const`` keyword adds general readability and safety to Python.

Rationale
=========

The inclusion of ``const`` provides significant benefits in both tooling and language semantics.

Compiler Benefits
-----------------

Globals Optimization
''''''''''''''''''''

If the compiler knows a global is ``const``, it can bake its value directly into the bytecode of functions that use it, rather than emitting ``LOAD_GLOBAL`` instructions.

Consider the following standard Python code::

DEBUG = False
def foo():
if DEBUG:
...
if DEBUG:
...

Currently, this results in repeated ``LOAD_GLOBAL`` instructions and runtime checks. With a ``const`` global, the compiler can store the value once, skip the ``LOAD_GLOBAL`` opcodes, and potentially use static analysis to identify and remove the dead branches entirely.

Class Safety and MRO Optimization
'''''''''''''''''''''''''''''''''

If a class method is marked ``const``, the compiler guarantees it will never be overridden by a subclass or shadowed by an instance attribute. When calling ``my_obj.const_method()``, the compiler does not need to check the instance dictionary or walk the Method Resolution Order (MRO). It can compile a direct call to that exact function object.

JIT Guard Reduction
'''''''''''''''''''

JIT compilers that rely on guards (such as CPython's JIT, torchdynamo, etc.) can emit fewer guards, as the invariants provided by ``const`` reduce the number of state changes that need monitoring.

Non-Compiler Benefits
---------------------

* **Readability**: Code becomes cleaner and easier to reason about.
* **Invariants**: Provides stronger invariants at the language level, reducing classes of bugs related to accidental mutation.

Specification
=============

This proposal pushes for the strictest version of ``const``-ness: forbidding both rebinding and direct mutation.

Syntax and Semantics
--------------------

Variables marked as ``const`` cannot be updated, and raise an exception upon update attempts.

Rebinding is forbidden::

const x = []
x = {} # Fails, no rebinding allowed, raises

Mutation is forbidden::

const x = []
x.append(1) # Fails, modifying the object declared as const, illegal

Function Arguments
------------------

An argument marked as ``const`` (whether a positional argument or a keyword argument) functions exactly as if a local variable were defined at the top of the function as ``const``.

Key behaviors include:

* **Immutability**: The variable cannot be modified, and the object it refers to cannot be updated or written to in any way.
* **Transitive Constness**: A ``const`` argument can only be passed to other functions that also expect it as ``const``. You cannot erase "constness" once it is applied.
* **Explicit Copying**: It can be copied out to a non-``const`` variable. This is the proposed analogue to C++'s ``const_cast``; the only way to "un-const" something is via a copy.

Shadowing or reassigning a ``const`` name is treated as an exception::

def foo(const bar, baz):
bar = 3 # Fails, raises on reassignment/shadowing
return bar * baz

When passing a ``const`` variable to another function, the receiving function's arguments must also be marked ``const``::

# Standard function with mutable arguments
def boo(bat, man):
...

def foo(const bar, baz):
boo(bar, baz) # Fails: raises on passing 'bar' to boo's 'bat',
# because 'bat' is not marked 'const'.
...

Class Attributes and Fields
---------------------------

Marking an attribute as ``const`` makes it writable only at ``__init__`` time (or assignable via a default value). It is illegal to modify a ``const`` attribute after initialization::

class MyWidget:
const x: int

def update(self, x):
self.x = x # Fails: always raises as 'self.x' is const

Variables
---------

Both local and global variables can be declared ``const``. This enforces the renaming and update semantics described above. Critically, these variables can only be passed to functions where the corresponding argument is also marked ``const``.

Backwards Compatibility
=======================

This proposal should be generally sound regarding backwards compatibility, except for cases where ``const`` is currently used as a variable name. This will become a ``SyntaxError``, which can be detected statically (via linting) and is relatively trivial to fix in existing codebases.

Security Implications
=====================

Introducing strict immutability may enhance security by preventing certain classes of injection or state-tampering attacks where mutable shared state is exploited. No negative security implications are immediately foreseen, though the implementation of "frozen" objects must ensure that standard Python sandboxing or restricted execution environments cannot bypass constness.

How to Teach This
=================

[Placeholder: Instructions for teaching this feature to new and experienced Python users.]

Reference Implementation
========================

The implementation is planned in phases (WIP):

* **Phase 1 (Rebinding):** Implementing standard "final" behavior. Bytecodes used for assignment (``STORE_FAST``, etc.) will be extended to look up const tagging and fail if necessary.
* **Phase 2 (Frozen Objects):** New bytecodes that set flags propagating the constness of the object to the underlying implementation. This will start with builtin types (``PyList``, ``PyDict``) and explore intercessions into functions like ``PyList_Append`` to respect the constness of the object.
* **Phase 3 (Viral Constness):** Implementing the transitive nature of const in function calls.

Rejected Ideas
==============

Less restrictive ``const`` (rebinding only)
-------------------------------------------

A variant of ``const`` was considered that only forbids rebinding, similar to "final" in Java or ``const`` in JavaScript.

.. code-block:: python

const x = []
x = {} # Fails, no rebinding allowed, raises
x.append(1) # Allowed, as the name `x` stays the same object

This was rejected because it does not resolve the mutable default problem presented in the Motivation, nor does it provide the strong invariants required for the proposed compiler optimizations.

Open Issues
===========

* **Viral Constness Implementation**: strictly enforcing transitive constness may incur a type check on every function call with const keywords in it, which may be prohibitively expensive or complex to implement efficiently.
* **Phase 2 Scope**: The extent to which "frozen" objects can be implemented across all C-extension types remains an open question.

Copyright
=========

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.
Loading