
On Thu, May 27, 2021 at 10:20 PM Steven D'Aprano steve@pearwood.info wrote:
Here is a sketch of how this could work, given a function like this:
def func(arg): static spam, eggs static cheese = expression ...
At function declaration time, the two static statements tell the compiler to:
- treat spam, eggs and cheese as local variables (use LOAD_FAST instead of LOAD_GLOBAL for lookups);
I don't think LOAD_FAST would be suitable here - isn't it always going to look in the stack frame?
- allocate static storage for them using the same (or similar) mechanism used for function default values;
Default values are attached to the function object (in either the __defaults__ tuple or the __kwdefaults__ dict).
spam and eggs get initialised as None;
cheese gets initialised to the value of `expression`, evaluated at function declaration time just as default arguments are.
When the function is called:
the interpreter automatically initialises the static variables with the stored values;
when the function exits (whether by return or by raising an exception) the static storage will be updated with the current values of the variables.
Hmm, I see what you mean. Not sure that this is really necessary though - and it could cause extremely confusing results with threading.
As a sketch of one possible implementation, the body of the function represented by ellipsis `...` might be transformed to this:
# initialise statics spam = LOAD_STATIC(0) eggs = LOAD_STATIC(1) cheese = LOAD_STATIC(2) try: # body of the function ... finally: STORE_STATIC(spam, 0) STORE_STATIC(eggs, 1) STORE_STATIC(cheese, 2)
One subtlety: what if the body of the function executes `del spam`? No problem: the spam variable will become undefined on the next function call, which means that subsequent attempts to get its value will raise UnboundLocalError:
try: x = spam + 1 except UnboundLocalError: spam = 0 x = 1
I would use this static feature if it existed. +1
Agreed, I'd use it too. But I'd define the semantics slightly differently:
* If there's an expression given, evaluate that when the 'def' statement is executed, same as default args * Otherwise it'll be uninitialized, or None, bikeshedding opportunity, have fun * Usage of this name uses a dedicated LOAD_STATIC or STORE_STATIC bytecode * The values of the statics are stored in some sort of high-performance cell collection, indexed numerically
It would be acceptable to store statics in a dict, too, but I suspect that that would negate some or all of the performance advantages. Whichever way, though, it should ideally be introspectable via a dunder attribute on the function.
Semantically, this would be very similar to writing code like this:
def count(): THIS_FUNCTION.__statics__["n"] += 1 return THIS_FUNCTION.__statics__["n"] count.__statics__ = {"n": 1}
except that it'd be more optimized (and wouldn't require magic to get a function self-reference).
Note that the statics *must* be defined on the function, NOT on the code object. Just like function defaults, they need to be associated with individual instances of a function.
f = [] for n in range(10):
... def spam(n=n): ... # static n=n # Same semantics ... print(n) ... f.append(spam) ...
Each spam() should print out its particular number, even though they all share the same code object.
This has been proposed a few times, never really got a lot of support though.
ChrisA