[Python-ideas] A "local" pseudo-function
Tim Peters
tim.peters at gmail.com
Tue May 1 23:51:05 EDT 2018
[MRAB]
>>> Would/should it be possible to inject a name into a local scope? You can't
>>> inject into a function scope, and names in a function scope can be
>>> determined statically (they are allocated slots), so could the same kind of
>>> thing be done for names in a local scope?
...
[Steven D'Aprano <steve at pearwood.info>]
> I don't know what MRAB means by "inject", but I know what *I* mean, and
> I have a real use-case for it.
>
> There is a long-running micro-optimization, often championed by Raymond,
> for avoiding slow global lookups (and even slower builtin lookups, since
> they require a global lookup to fail first) by turning them in local
> lookups at function-definition time.
Before nested scopes, it was also used to help nested functions call
each other; e.g.,
def f():
def g():
...
# h needs to call g, but can't see f's locals
def h(g=g): # but default arg expressions can see f's locals
g() # works great :-)
> E.g. some methods from the random.Random class:
>
> def randrange(self, start, stop=None, step=1, _int=int):
> ...
> def _randbelow(self, n, int=int, maxsize=1<<BPF, type=type,
> Method=_MethodType, BuiltinMethod=_BuiltinMethodType):
> ...
>
> (copied from 3.5, I don't know what the most recent version looks like)
Doesn't matter; code like that has been in the std lib since the
start, although Raymond is inordinately fond of it ;-)
> That's a nice way to make the binding:
>
> _int = int
>
> occur once only, instead of putting it inside the function body which
> then needs to be executed on ever call. Effectively it's a static
> variable for the method, one which persists from one call to the next
> without requiring re-initialisation.
>
> But it's ugly :-(
Error-prone too, because absolutely nothing stops a user from calling
these things with more positional arguments than are intended. I
don't want to bother looking now, but there was _some_ "bug report"
complaining that one of these "default arguments" went away somewhere,
which a user was specifying intentionally to override some accidental
implementation detail.
However, while it's dead obviously "error prone" in theory, it's
remarkable how few times I've heard of anyone getting burned by it in
practice.
> The function signature is full of *implementation details* instead of
> the parameters needed for the method's interface. Look at _randbelow
> which takes one actual parameter, n, plus FIVE fake parameters, int,
> maxsize, type, Method and BuiltinMethod, none of which should ever be
> passed arguments.
>
> So when I talk about injecting values into a function, that is the sort
> of thing I'm referring to: at function definition time, push or inject a
> reference to a known value (say, the builtin int) into something which
> behaves as a static variable.
>
> It would be nice if we could do that without polluting the function
> signature.
I'm surprised that hasn't already been done.
> I'll admit that the name "inject" came to me when I was
> thinking of some hypothetical decorator:
>
> @inject(int=int, maxsize=1<<BPF, type=type, ...)
> def _randbelow(self, n):
> ...
>
> that somehow pushed, or *injected*, those bindings into the function,
> turning them into locals, but in an alternative universe where Guido
> loved making new keywords, I'd use a static initialisation block and
> stick it inside the def:
>
> def _randbelow(self, n):
> static:
> # this gets executed once only, at function definition time
> int=int
> maxsize=1<<BPF
> type=type
> # body of _randbelow
Blame computer scientists. I started my paying career working on Cray
Research's Fortran compiler, a language in which NO words were
reserved. The syntax was such that it was always possible to
determine whether a language keyword or a user-supplied name was
intended. That worked out great.
But it seemed to require ad hoc parsing tricks. Computer scientists
invented "general" parser generators, and then every new language
started declaring that some words were reserved for the language's
use. That was just to make things easier for parser-generator
authors, not for users ;-)
Maybe we could steal a trick from the way the async statements ("async
def", "async for", "async with") were added without making "async" a
reserved word? Even if user code already uses the name "static", just
as in Fortran the only way to parse
static:
that makes sense is that it's introducing a block;
USER_NAME:
as a statement has no meaning. In any other context, the
block-opening meaning of "static" makes no sense, so it must mean a
user-defined name then. We could also add, e.g.,
MRAB local a, b, c:
and
Uncle Timmy not really local at all a, b, c:
without introducing any actual ambiguity in code already using any of
the leading words on those lines.
It would be fun to deprive Guido of the dead easy "but it's a new
reserved word!" way to veto new features ;-)
More information about the Python-ideas
mailing list