Skip to content

Commit e46ffcb

Browse files
authored
Found out about PEP 12
1 parent ec3d22c commit e46ffcb

1 file changed

Lines changed: 84 additions & 139 deletions

File tree

peps/pep-0812.rst

Lines changed: 84 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,21 @@ Status: Draft
55
Type: Standards Track
66
Content-Type: text/x-rst
77
Created: 31-Oct-2025
8+
Python-Version: 3.15
9+
Post-History: Pending
10+
Discussions-To: Pending
811

912
Abstract
1013
========
1114

12-
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, the first of which is 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 opitmizations aside, ``const`` hints ignal to programmers that the original author of the code intended this object not to change, which, like c++'s ``const`` corectness ideas, promotes safety and readability. Potential general programming benefits aside, a far more specific python "Gotcha", is mutable defaults.
15+
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.
1316

14-
This PEP proposes a ``const`` keyword that can be inserted in function arguments, defaults, and in scopes that declares an object as immutable.
15-
16-
Proposal
17-
========
18-
19-
A ``const`` keyword that applies to functions, class attributes, and variables.
17+
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.
2018

2119
Motivation
2220
==========
2321

24-
To elaborate on the cases above, consider the following code::
22+
Consider the following code::
2523

2624
def add_item_to_cart(item, cart=[]):
2725
"""
@@ -31,9 +29,9 @@ To elaborate on the cases above, consider the following code::
3129
cart.append(item)
3230
return cart
3331

34-
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.
32+
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 fortha common mistake.
3533

36-
Or::
34+
Another example involves accidentally mutating data that should be a snapshot::
3735

3836
def analyze_latest_scores(current_scores):
3937
original_order = current_scores
@@ -43,70 +41,69 @@ Or::
4341
"first_entry": original_order[0]
4442
}
4543

46-
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 is will be the top score, not the first entry!
44+
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.
4745

48-
And, aside from these edge cases of mutability, just general readability and safety added to python.
46+
Beyond these edge cases of mutability, a ``const`` keyword adds general readability and safety to Python.
4947

50-
What does ``const`` mean?
51-
=========================
48+
Rationale
49+
=========
5250

53-
There are two tiers of ``const``-ness - this proposal pushes for the strictest version of it.
51+
The inclusion of ``const`` provides significant benefits in both tooling and language semantics.
5452

55-
Less restrictive - ``const`` only forbids rebinding
56-
---------------------------------------------------
53+
Compiler Benefits
54+
-----------------
5755

58-
In this variant of ``const``, we limit it to mean rebinding. It is closer spiritually to "final" in certain other languages.
56+
Globals Optimization
57+
''''''''''''''''''''
5958

60-
.. code-block:: python
59+
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.
6160

62-
const x = []
63-
x = {} # Fails, no rebinding allowed, raises
61+
Consider the following standard Python code::
6462

65-
However::
63+
DEBUG = False
64+
def foo():
65+
if DEBUG:
66+
...
67+
if DEBUG:
68+
...
6669

67-
const x = []
68-
x.append(1) # Sound, allowed, as the name `x` stays the same type and object, it merely got mutated
70+
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.
6971

70-
In this case, theres not much to do with function arguments, except catch shadowing as an exception.
71-
In this case, the mutable default problem presented above is not resolved.
72+
Class Safety and MRO Optimization
73+
'''''''''''''''''''''''''''''''''
7274

73-
More restrictive - ``const`` forbids direct mutation
74-
----------------------------------------------------
75+
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.
7576

76-
.. code-block:: python
77+
JIT Guard Reduction
78+
'''''''''''''''''''
7779

78-
const x = []
79-
x = {} # Fails, no rebinding allowed, raises
80+
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.
8081

81-
And::
82+
Non-Compiler Benefits
83+
---------------------
8284

83-
const x = []
84-
x.append(1) # Fails, modifying the object declared as const, illegal
85+
* **Readability**: Code becomes cleaner and easier to reason about.
86+
* **Invariants**: Provides stronger invariants at the language level, reducing classes of bugs related to accidental mutation.
8587

86-
And::
88+
Specification
89+
=============
8790

88-
class MyWidget:
89-
x: int
91+
This proposal pushes for the strictest version of ``const``-ness: forbidding both rebinding and direct mutation.
9092

91-
def update(self, x):
92-
self.x = x
93+
Syntax and Semantics
94+
--------------------
9395

94-
m = MyWidget()
95-
m.update(1) # 1, sound
96-
m.update(2) # 2, sound
97-
const n = MyWidget()
98-
n.update(1) # Fails, updating a const
96+
Variables marked as ``const`` cannot be updated, and raise an exception upon update attempts.
9997

100-
Variables marked as ``const`` cannot be updated, and raise upon updated
98+
Rebinding is forbidden::
10199

102-
Usage
103-
=====
100+
const x = []
101+
x = {} # Fails, no rebinding allowed, raises
104102

105-
There are three primary uses of the ``const`` keyword proposed here:
103+
Mutation is forbidden::
106104

107-
* On function arguments
108-
* On attributes and fields classes
109-
* On variables
105+
const x = []
106+
x.append(1) # Fails, modifying the object declared as const, illegal
110107

111108
Function Arguments
112109
------------------
@@ -119,21 +116,13 @@ Key behaviors include:
119116
* **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.
120117
* **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.
121118

122-
Reassignment and Shadowing
123-
^^^^^^^^^^^^^^^^^^^^^^^^^^
124-
125-
126-
Shadowing or reassigning a ``const`` name is treated as an exception.
127-
128-
.. code-block:: python
119+
Shadowing or reassigning a ``const`` name is treated as an exception::
129120

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

134-
When passing a ``const`` variable to another function, the receiving function's arguments must also be marked ``const``.
135-
136-
.. code-block:: python
125+
When passing a ``const`` variable to another function, the receiving function's arguments must also be marked ``const``::
137126

138127
# Standard function with mutable arguments
139128
def boo(bat, man):
@@ -147,9 +136,7 @@ When passing a ``const`` variable to another function, the receiving function's
147136
Class Attributes and Fields
148137
---------------------------
149138

150-
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.
151-
152-
.. code-block:: python
139+
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::
153140

154141
class MyWidget:
155142
const x: int
@@ -160,98 +147,56 @@ Marking an attribute as ``const`` makes it writable only at ``__init__`` time (o
160147
Variables
161148
---------
162149

163-
As covered in previous sections, both local and global variables can be declared ``const``. This enforces the renaming and update semantics described above.
150+
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``.
164151

165-
Critically, these variables can only be passed to functions where the corresponding argument is also marked ``const``.
152+
Backwards Compatibility
153+
=======================
166154

167-
Benefits
168-
========
155+
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.
169156

170-
Compiler Benefits
171-
-----------------
157+
Security Implications
158+
=====================
172159

173-
Globals Optimization
174-
^^^^^^^^^^^^^^^^^^^^
160+
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.
175161

176-
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.
177-
178-
Consider the following standard Python code:
179-
180-
.. code-block:: python
162+
How to Teach This
163+
=================
181164

182-
DEBUG = False
183-
def foo():
184-
if DEBUG:
185-
...
186-
if DEBUG:
187-
...
165+
[Placeholder: Instructions for teaching this feature to new and experienced Python users.]
188166

189-
Currently, this results in repeated ``LOAD_GLOBAL`` instructions and runtime checks:
167+
Reference Implementation
168+
========================
190169

191-
.. code-block:: text
170+
The implementation is planned in phases (WIP):
192171

193-
Disassembly of <code object foo at 0x561367afd590, file "example.py", line 2>:
194-
2 RESUME 0
172+
* **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.
173+
* **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.
174+
* **Phase 3 (Viral Constness):** Implementing the transitive nature of const in function calls.
195175

196-
3 LOAD_GLOBAL 0 (DEBUG)
197-
TO_BOOL
198-
POP_JUMP_IF_FALSE 1 (to L1)
176+
Rejected Ideas
177+
==============
199178

200-
4 NOP
179+
Less restrictive ``const`` (rebinding only)
180+
-------------------------------------------
201181

202-
5 L1: LOAD_GLOBAL 0 (DEBUG)
203-
TO_BOOL
204-
POP_JUMP_IF_FALSE 1 (to L2)
182+
A variant of ``const`` was considered that only forbids rebinding, similar to "final" in Java or ``const`` in JavaScript.
205183

206-
6 RETURN_CONST 0 (None)
207-
208-
5 L2: RETURN_CONST 0 (None)
209-
210-
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.
211-
212-
Class Safety and MRO Optimization
213-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
214-
If a class method is marked ``const``, the compiler guarantees it will never be overridden by a subclass or shadowed by an instance attribute.
215-
216-
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.
217-
218-
JIT Guard Reduction
219-
~~~~~~~~~~~~~~~~~~~
220-
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.
221-
222-
Non-Compiler Benefits
223-
---------------------
184+
.. code-block:: python
224185
225-
* **Readability**: Code becomes cleaner and easier to reason about.
226-
* **Invariants**: Provides stronger invariants at the language level, reducing classes of bugs related to accidental mutation.
186+
const x = []
187+
x = {} # Fails, no rebinding allowed, raises
188+
x.append(1) # Allowed, as the name `x` stays the same object
227189
190+
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.
228191

229-
Back-compat
192+
Open Issues
230193
===========
231194

232-
Should be entirely sound - except for cases where someone is using ``const`` as a variable name. This should become a SyntaxError, which should be relatively trivial to fix, and can be detected entirely statically (linting, etc).
233-
234-
235-
Implementation / Open questions
236-
===============================
237-
238-
Note - this section needs further exploration and is a WIP.
239-
240-
Basics
241-
------
242-
The implementation would require adding const to the python grammar, updating the ast, and all other language level structures that handle keywords.
243-
244-
Less restrictive / phase 1
245-
--------------------------
246-
The first phase, rebinding, (or, breaking rebinding, aka, the final keyword like work described above) - seems relatively straightforward. Bytecodes used for assignment (store_fast, etc) - would be extended to look up our const tagging and fail according to the descriptions above.
247-
248-
249-
Frozen objects / phase 2
250-
------------------------
251-
For the second phase, more akin to a frozen object, we would need to come up with new bytecodes that set flags that propagate the constness of the object to the underlying implementation. I think we would start with builtin types (PyList, PyDict) and start exploring intercessions into functions like PyList_Append to respect the constness of the object.
252-
195+
* **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.
196+
* **Phase 2 Scope**: The extent to which "frozen" objects can be implemented across all C-extension types remains an open question.
253197

254-
Viral constness / phase 3
255-
-------------------------
256-
Viral constness seems tricky to implement, as it would incur a type check on every function call with const keywords in it.
198+
Copyright
199+
=========
257200

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

0 commit comments

Comments
 (0)