
I think maybe the idiomatic pattern should be assignment rather than just bare name. E.g. f = delayed 1 + 2 # We want to evaluate f before next op for some reason *f = f* # f is already a concrete value now, before calculating g g = f * 7 I think if we follow my rule that "everything lazy within delayed expression stays unevaluated" that needs to apply to function calls too. So I think in: x = delayed my_lazy_func(b, c) This would not affect the laziness of 'b' or 'c' with that line. It also would not run any flow control within the function, etc. Basically, it's almost like wrapping it in a lambda: x = lambda: my_lazy_func(b, c) Except that when you finally *do* want the value out of 'x' you don't spell that as 'x() + 7' but simply as 'x + 7'. This also means that the above delayed function call would be functionally identical if you spelled it: x = delayed my_lazy_func(delayed b, delayed c) This also means that a 'delayed' object needs to be idempotent. So x = delayed 2+2 y = delayed x z = delayed delayed delayed y Wrapping more delays around an existing delayed object should probably just keep the same object rather than "doubly delaying" it. If there is some reason to create separate delayed objects that isn't occurring to me, evaluating 'z' would still go through the multiple evaluation levels until it got to a non-delayed value. On Thu, Feb 16, 2017 at 10:55 PM, David Mertz <mertz@gnosis.cx> wrote:
On Thu, Feb 16, 2017 at 10:33 PM, Joshua Morton <joshua.morton13@gmail.com
wrote:
David, can you elaborate on your example?
if we replaced line four with
>>> x = my_lazy_func(b, delayed c)
what would the value of `x` be, and how would this differ from either
The value of the function would be whatever you want, including another delayed object, potentially.
Here's a toy function just to show the pattern I have in mind:
def my_lazy_func(x, y):
if x == 0: # evaluate y if it was lazy, or just return plain value
return y
elif x > 0:
# create a lazy object whether y was lazy or concrete
# if y *was* lazy, it remains so in delayed expression
return delayed y + 17
elif x < -10:
# evaluate y, if delayed, by virtue of being part of
# plain non-delayed RHS expression
z = y * 6
return z
else:
# y never mentioned, so if it was lazy nothing changes
return x - 22
Whether or not 'c' (called 'y' within the function scope) gets evaluated/computed depends on which branch was taken within the function. Once the delayed computation is done, the object at the delayed address becomes concrete thereafter to avoid repeated (and potentially stateful or side-effect causing evaluation).
One question is whether you'd need to write:
m = delayed 1 + 2 n = delayed m * 3 # This seems ugly and unnecessary (but should work) q = delayed m / delayed n # This seems better (does not evaluate m or n) # everything lazy within delayed expression stays unevaluated
q2 = delayed m/n
If you ever need to explicitly evaluate a delayed object, it is as simple as putting a name pointing to it on a line by itself:
f = delayed 1 + 2
# We want to evaluate f before next op for some reason
f
# f is already a concrete value now, before calculating g
g = f * 7
So for example, in a hypothetical Python 3.7+:
a = delayed 1 + 2
b = delayed b * 3
c = delayed 12/3
my_lazy_func(b, delayed c) # evaluates b but not yet c b 9 delayed c <delayed object at 0x123456789>
If you want to do something like Dask... or for Dask itself to be able to use it in some eventual version, you'd need to be able to keep objects from evaluating even while you passed them around. The obvious use is for finding parallelism, but other things like passing callbacks might find this useful too.
Dask delayed objects stay lazy until you explicitly `.compute()` on them (which does so recursively on every lazy object that might go into the computation). This hypothetical new keyword would have object evaluate eagerly *unless* you explicitly kept them lazy. But the idea is that the programmer would still get to decide in their code.
On Feb 16, 2017 9:53 PM, "Joseph Jevnik" <joejev@gmail.com> wrote:
You might be interested in https://github.com/llllllllll/lazy_python, which implements the features you describe but instead of a keyword it uses a decorator.
On Fri, Feb 17, 2017 at 12:24 AM, Joseph Hackman < josephhackman@gmail.com> wrote:
Howdy All!
This suggestion is inspired by the question on "Efficient debug logging".
I propose a keyword to mark an expression for delayed/lazy execution, for the purposes of standardizing such behavior across the language.
The proposed format is: delayed: <expr> i.e. log.info("info is %s", delayed: expensiveFunction())
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, or any method on the delayed object is called, the expression is executed and the delayed expression is replaced with the result. (Thus, the delayed expression is only every evaluated once).
Ideally: a = delayed: 1+2 b = a print(a) #adds 1 and 2, prints 3 # a and b are now both just 3 print(b) #just prints 3
Mechanically, this would be similar to the following:
class Delayed(): def __init__(self, func): self.__func = func self.__executed = False self.__value = None
def __str__(self): if self.__executed: return self.__value.__str__() self.__value = self.__func() self.__executed = True return self.__value.__str__()
def function_print(value): print('function_print') print(value)
def function_return_stuff(value): print('function_return_stuff') return value
function_print(function_return_stuff('no_delay'))
function_print(Delayed(lambda: function_return_stuff('delayed')))
delayed = Delayed(lambda: function_return_stuff('delayed_object')) function_print(delayed) function_print(delayed)
Unfortunately, due to https://docs.python.org/3/r eference/datamodel.html#special-lookup , this magic delayed class would need to implement many magic methods, as __getattribute__ is not _always_ called.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.