On Thu, May 27, 2021 at 11:21:26PM +1000, Chris Angelico wrote:
> On Thu, May 27, 2021 at 10:20 PM Steven D'Aprano <st...@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?

Where else should it look?

I'll admit I'm not an expert on the various LOAD_* bytecodes, but I'm 
pretty sure LOAD_FAST is used for local variables. Am I wrong?

My idea is that the static variable should be a local variable that gets 
saved on function exit and restored on function entry. Is there another 
concept of static variables that I should know about?


> > * 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).


Right. In principle we could just shove the static values in the 
__defaults__ tuple, but it's probably better to use a distinct 
__statics__ dunder.


> > * 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 - 

If you don't store the values away somewhere on function exit, how do 
you expect them to persist from one call to the next? Remember that they 
are fundamentally variables -- they should be able to vary from one call 
to the next.


> and it could cause extremely confusing results with
> threading.

Don't we have the same issues with globals, function attributes, and 
instance attributes?

I'm okay with saying that if you use static *variables* (i.e. they 
change their value from one call to the next) they won't be thread-safe. 
As opposed to static "constants" that are only read, never written.

But if you have a suggestion for a thread-safe way for functions to 
keep variables alive from one call to the next, that doesn't suffer a 
big performance hit, I'm all ears :-)


> 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

That's what I said, except I called it function definition time :-)


> * Otherwise it'll be uninitialized, or None, bikeshedding opportunity, have 
> fun

I decided on initialising them to None because it is more convenient to 
write:

    if my_static_var is None:
        # First time, expensive computation.
        ...

than the alternative with catching an exception. YMMV.


> * 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

Isn't that what LOAD_FAST already does?



> 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.

Maybe I'm misunderstanding you, or you me. Let me explain further 
what I think can happen.

When the function is being executed, I think that static variables 
should be treated as local variables. Why build a whole second 
implementation for fast cell-based variables, with a separate set of 
bytecodes, to do exactly what locals and LOAD_FAST does? We should reuse 
the existing fast local variable machinery, not duplicate it.

The only part that is different is that those static locals have to be 
automatically initialised on function entry (like parameters are), and 
then on function exit their values have to be stored away in a dunder so 
they aren't lost (as plain local variables are lost when the function 
exists). That bit is new.

We already have a mechanism to initialise locals: it's used for default 
values. The persistent data is retrieved from the appropriate dunder on 
the function and bound to the local variable (parameter). We can do the 
same thing. We will probably use a different dunder.

That doesn't mean that every single access to the local static variable 
needs to be retrieved from the dunder, that would likely be slow. Only 
on function entry.

The difference between function default arguments and statics is that if 
you rebind the parameter, that new value doesn't get written out to the 
__defaults__ dunder on function exit. But for statics, it should be.

Are we on the same page here?



> 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).

The most obvious optimization is that you only write the static value 
out to the dunder on function exit, not on ever rebinding operation.


> 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.

Absolutely.



-- 
Steve
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/TNTZ36L56HMTIZ2IP5VK6TKOMWH2OHT5/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to