[Python-ideas] Delayed Execution via Keyword

Steven D'Aprano steve at pearwood.info
Fri Feb 17 20:23:46 EST 2017


On Fri, Feb 17, 2017 at 06:06:26PM -0500, Joseph Hackman wrote:

[...]
> I think it would be key, like async/await, to narrowly define the scope in
> which the word delayed functions as a keyword.

The PEP makes it clear that's just a transition phase: they will be 
turned into proper keywords in Python 3.7.

https://www.python.org/dev/peps/pep-0492/#id80

Python has had "pseudo-keywords" in the past, like "as":

[steve at ando ~]$ python2.5 -c "import math as as; print as"
<module 'math' from '/usr/local/lib/python2.5/lib-dynload/math.so'>

and it is my understanding that the core developers dislike this sort of 
thing. As do I. You shouldn't count as getting the same special treament 
as async/await. Maybe you will, maybe you won't.


> > A new keyword means it can't be back-ported to older versions, and will
> > break code.
> >
> >
> async and await both work fine, for the reasons listed above.

You're missing the point: code that uses async and await, whether as 
pseduo-keywords or actual keywords, cannot easily be backported to 
Python 3.4 or older.

If Python introduces a new built-in, say Aardvark, then it can be 
back-ported:

try:
    Aardvark
except NameError:
    from backport import Aardvark

No such thing is possible for new syntax. So that counts as a 
disadvantage of new syntax. Are we positive that there *must* be new 
syntax to solve this problem?

(I think probably so, but it still counts as a disadvantage: that means 
that the usefulness is reduced.)


> > > Unlike 'lambda' which returns a function (so the receiver must be
> > > lambda-aware), delayed execution blocks are for all purposes values. The
> > > first time the value (rather than location) is read,
> >
> > What counts as "reading" a value? Based on your example below, I can't
> > tell if passing the object to *any* function is enough to trigger
> > evaluation, or specifically print is the magic that makes it happen.
> 
> So far I'm going with pretty much anything that isn't being the right-hand
> of an assignment. So coercion to different types, hashing (for use as a key
> in a dict or set), __repr__, etc would all be covered, as well as identity
> and comparisons. i.e.:
[...]


That will make it pretty much impossible to tell whether something is a 
delayed "thunk" or not, since *any* attempt to inspect it in any way 
will cause it to reify.

Maybe that's what we want.


> > That's easily done by having the "delayed" keyword cache each expression
> > it sees, but that seems like a bad idea to me:
> >
> > spam = delayed: get_random_string()
> > eggs = delayed: get_random_string()  # the same expression
> >
> > spam.upper()  # convert to a real value
> >
> > assert spam == eggs  # always true, as they are the same expression
> >
> 
> Since spam and eggs are two different instances of delayed expression, each
> one would be evaluated separately when they are read from (as operands for
> the equals operator). So no, even without the spam.upper(), they would not
> match.

Earlier we talked about delayed *expressions* always generating the same 
value, now you're talking about *instances* rather than expressions. It 
makes sense to have keep the standard Python object semantics, rather 
than have the value of a delayed thunk cached by the textual expression 
that generated it.


> > I think it is better to stick to a more straight-forward, easily
> > understood and debugged system based on object identity rather than
> > expressions.
> >
> >
> The caching means that:
> spam  = delayed: calculate(1)
> eggs = spam
> 
> eggs == spam would be true, and calculate would have only been called once,
> not twice.

That's not caching, that's simple identity. That's how assignment works 
in Python, delayed calculation or not.


-- 
Steve


More information about the Python-ideas mailing list