I like the focus on the high-level programming model in the revamped PEP. It helps highlight some useful semantics that we may want to see in future Python. There are certain points in the PEP that hint at a different way than how things are handled in the language now, which I think will be worth discussing first in their own merits.
Freezing functions is particularly interesting to me. Listing 14 in the PEP:
# pure function -- freezing will not propagate
def function1(a, b):
return a + b
# freezing will propagate to function1
def function2(a, b):
return function1(a, b)
freeze(x)
# only captures immutable state -- freezing will not propagate
def function3(a):
a.append(x)
# freezing will propagate to y
def function4(a):
y.append(a) # Note -- will throw exception always when called
Currently function (and code) objects in Python are dynamic in the sense that they donât actually hold references to the objects referred by the closures in their body (free variables, globals, builtins). What they do effectively hold is the same live globals dict as the module through which they do the name lookups. The bytecode also shows that in function2 the function1 is a global name lookup. y in functon4 is also a global name lookup, while the append methods in function3 and function4 are attribute lookups.
dis.dis() of Listing 14
0 RESUME 0
2 LOAD_CONST 0 (<code object function1 at 0x000001FDD0174B70, file "<dis>", line 2>)
MAKE_FUNCTION
STORE_NAME 0 (function1)
6 LOAD_CONST 1 (<code object function2 at 0x000001FDD0174C60, file "<dis>", line 6>)
MAKE_FUNCTION
STORE_NAME 1 (function2)
9 LOAD_NAME 2 (freeze)
PUSH_NULL
LOAD_NAME 3 (x)
CALL 1
POP_TOP
11 LOAD_CONST 2 (<code object function3 at 0x000001FDD016CD30, file "<dis>", line 11>)
MAKE_FUNCTION
STORE_NAME 4 (function3)
15 LOAD_CONST 3 (<code object function4 at 0x000001FDD016F830, file "<dis>", line 15>)
MAKE_FUNCTION
STORE_NAME 5 (function4)
LOAD_CONST 4 (None)
RETURN_VALUE
Disassembly of <code object function1 at 0x000001FDD0174B70, file "<dis>", line 2>:
2 RESUME 0
3 LOAD_FAST_BORROW_LOAD_FAST_BORROW 1 (a, b)
BINARY_OP 0 (+)
RETURN_VALUE
Disassembly of <code object function2 at 0x000001FDD0174C60, file "<dis>", line 6>:
6 RESUME 0
7 LOAD_GLOBAL 1 (function1 + NULL)
LOAD_FAST_BORROW_LOAD_FAST_BORROW 1 (a, b)
CALL 2
RETURN_VALUE
Disassembly of <code object function3 at 0x000001FDD016CD30, file "<dis>", line 11>:
11 RESUME 0
12 LOAD_FAST_BORROW 0 (a)
LOAD_ATTR 1 (append + NULL|self)
LOAD_GLOBAL 2 (x)
CALL 1
POP_TOP
LOAD_CONST 0 (None)
RETURN_VALUE
Disassembly of <code object function4 at 0x000001FDD016F830, file "<dis>", line 15>:
15 RESUME 0
16 LOAD_GLOBAL 0 (y)
LOAD_ATTR 3 (append + NULL|self)
LOAD_FAST_BORROW 0 (a)
CALL 1
POP_TOP
LOAD_CONST 0 (None)
RETURN_VALUE
By deep-freezing functions we may want to lock down the name lookups so that they are resolved and bound eagerly at freeze-time. This means we have to
- eliminate closures (by means of having the code object bind actual references to the objects referred by the names, like how methods are bound to the self object),
- or at least freeze their namespaces (which depending on case may not be possible)
Assuming we read append in the above examples as a non-mutating method called my_method, the dot attribute lookup on global name (y.my_method ) in function4 can be frozen, but not a.my_methodin function3 which must remain virtual dispatch. If we choose to eliminate closures by means static dispatch of y.my_method, this will allow the interpreter to elide a global name lookup at invoke-time and may actually be useful for optimization projects like JIT, which will now be able to âsee throughâ a code objectâs name bindings more easily and provide better specialization. But doing things this way doesnât work well[1] with PEP 810 â Explicit lazy imports | peps.python.org where any lazy import will need to be resolved eagerly by function deep freezing.
The compatibility matrix of the multiple projects trying to enable parallelism and/or optimization into Python (free-threading, subinterpreters, JIT, lazy import and freezing) is only to get more complicated over time. âŠď¸