Skip to content

Commit ec3d22c

Browse files
authored
Update pep-0812.rst
1 parent 8cb4dc4 commit ec3d22c

1 file changed

Lines changed: 129 additions & 23 deletions

File tree

peps/pep-0812.rst

Lines changed: 129 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -108,44 +108,150 @@ There are three primary uses of the ``const`` keyword proposed here:
108108
* On attributes and fields classes
109109
* On variables
110110

111-
On function arguments
112-
---------------------
111+
Function Arguments
112+
------------------
113+
114+
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``.
115+
116+
Key behaviors include:
113117

114-
An argument marked as ``const``, be it an arg or a kwarg, functions exactly as if you were to define a local variable at the top of the function as ``const``. It cannot be modified, and the object it refers to cannot be updated or written to in any way. It can only be passed to functions that also expect it as "``const``" - that is, you cannot erase ``const``ness once it is applied. It can be copied out to a non ``const`` variable, and that is the proposed analogue of ``const_cast`` here, the only way to un-``const`` something is via a copy.
118+
* **Immutability**: The variable cannot be modified, and the object it refers to cannot be updated or written to in any way.
119+
* **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.
120+
* **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.
115121

116-
Shadowing a name becomes an exception.
122+
Reassignment and Shadowing
123+
^^^^^^^^^^^^^^^^^^^^^^^^^^
124+
125+
126+
Shadowing or reassigning a ``const`` name is treated as an exception.
117127

118128
.. code-block:: python
119129
120-
def foo(const bar, baz):
121-
bar = 3 # Fails, raises on shadowing
122-
return bar * baz
130+
def foo(const bar, baz):
131+
bar = 3 # Fails, raises on reassignment/shadowing
132+
return bar * baz
133+
134+
When passing a ``const`` variable to another function, the receiving function's arguments must also be marked ``const``.
123135

124136
.. code-block:: python
125137
126-
def boo(bat, bat):
127-
...
138+
# Standard function with mutable arguments
139+
def boo(bat, man):
140+
...
128141
129-
def foo(const bar, baz):
130-
boo(bar, bar) # Fails, raises on passing bar to boo's bat, which is not `const`
131-
...
142+
def foo(const bar, baz):
143+
boo(bar, baz) # Fails: raises on passing 'bar' to boo's 'bat',
144+
# because 'bat' is not marked 'const'.
145+
...
132146
133-
On attributes and fields in classes
134-
-----------------------------------
147+
Class Attributes and Fields
148+
---------------------------
135149

136-
This makes the attribute only writable at __init__ time - or assignable with a default. It is illegal to modify a ``const`` variable after.
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.
137151

138152
.. code-block:: python
139153
140-
class MyWidget:
141-
const x: int
154+
class MyWidget:
155+
const x: int
142156
143-
def update(self, x):
144-
self.x = x # Fails, always raises, x is const
157+
def update(self, x):
158+
self.x = x # Fails: always raises as 'self.x' is const
159+
160+
Variables
161+
---------
162+
163+
As covered in previous sections, both local and global variables can be declared ``const``. This enforces the renaming and update semantics described above.
164+
165+
Critically, these variables can only be passed to functions where the corresponding argument is also marked ``const``.
166+
167+
Benefits
168+
========
169+
170+
Compiler Benefits
171+
-----------------
172+
173+
Globals Optimization
174+
^^^^^^^^^^^^^^^^^^^^
175+
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
181+
182+
DEBUG = False
183+
def foo():
184+
if DEBUG:
185+
...
186+
if DEBUG:
187+
...
188+
189+
Currently, this results in repeated ``LOAD_GLOBAL`` instructions and runtime checks:
190+
191+
.. code-block:: text
192+
193+
Disassembly of <code object foo at 0x561367afd590, file "example.py", line 2>:
194+
2 RESUME 0
195+
196+
3 LOAD_GLOBAL 0 (DEBUG)
197+
TO_BOOL
198+
POP_JUMP_IF_FALSE 1 (to L1)
199+
200+
4 NOP
201+
202+
5 L1: LOAD_GLOBAL 0 (DEBUG)
203+
TO_BOOL
204+
POP_JUMP_IF_FALSE 1 (to L2)
205+
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+
---------------------
224+
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.
227+
228+
229+
Back-compat
230+
===========
231+
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.
145252

146-
On variables
147-
------------
148253

149-
Mostly covered above, but either a local or global can be declared ``const``, and enforces renaming and update semantics described above.
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.
150257

151-
Can only be passed functions where the argument is marked ``const``.

0 commit comments

Comments
 (0)