On Fri, 24 Jun 2022 at 13:26, Joao S. O. Bueno firstname.lastname@example.org wrote:
On Thu, Jun 23, 2022 at 2:53 AM Chris Angelico email@example.com wrote:
On Thu, 23 Jun 2022 at 11:35, Joao S. O. Bueno firstname.lastname@example.org wrote:
Martin Di Paola wrote:
Three cases: Dask/PySpark, Django's ORM and selectq. All of them implement deferred expressions but all of them "compute" them in very specific ways (aka, they plan and execute the computation differently).
So - I've been hit with the "transparency execution of deferred code" dilemma before.
What happens is that: Python, at one point will have to "use" an object - and that use is through calling one of the dunder methods. Up to that time, like, just writing the object name in a no-operation line, does nothing. (unless the line is in a REPL, which will then call the __repr__ method in the object).
Why are dunder methods special? Does being passed to some other function also do nothing? What about a non-dunder attribute?
Non-dunder attributes goes through obj.__getattribute__ at which point evaluation is triggered anyway.
Hmm, do they actually, or is that only if it's defined? But okay. In that case, simply describe it as "accessing any attribute".
Especially, does being involved in an 'is' check count as using an object?
"is" is not "using', and will be always false or true as for any other object. Under this approach, the delayed object is a proxy, and remains a proxy, so this would have side-effects in code consuming the object. (extensions expecting strict built-in types might not work with a proxy for an int or str) - but "is" comparison should bring 0 surprises.
At this point, I'm wondering if the proposal's been watered down to being nearly useless. You don't get the actual object, it's always a proxy, and EVERY attribute lookup on EVERY object has to first check to see if it's a special proxy.
dflt = fetch_cached_object("default") mine = later fetch_cached_object(user.keyword) ... if mine is dflt: ... # "using" mine? Or not?
Does it make a difference whether the object has previously been poked in some other way?
In this case, "mine" should be a proxy for the evaluation of the call of "fetch_cached_object" which clearly IS NOT the returned object stored in "dflt".
This is so little, or so much, surprising as verifying that "bool()" yields False: it just follows the language inner workings, with not special casing.
If it's defined as a proxy, then yes, that's the case - it will never be that object, neither before nor after the undeferral. But that means that a "later" expression will never truly become the actual object, so you always have to keep that in mind. I foresee a large number of style guides decrying the use of identity checks because they "won't work" with deferred objects.
Of course, this if this proposal goes forward - I am just pointing that the existing mechanisms in the language can already support it in a way with no modification. If "is" triggering the resolve is desired, or if is desired the delayed object should be replaced "in place", instead of using a proxy, another approach would be needed - and I'd favor the "already working" proxy approach I presented here.
(I won't dare touch the bike-shedding about the syntax on this, though)
Right, but if the existing mechanisms are sufficient, why not just use them? We *have* lambda expressions. It wouldn't be THAT hard to define a small wrapper - okay, the syntax is a bit clunky, but bear with me:
class later: def __init__(self, func): self.func = func self.__is_real = False def __getattribute__(self, attr): self.__makereal() return getattr(self.__wrapped, attr) def __makereal(self): if self.__is_real: return self.__wrapped = self.func() self.__is_real = True
x = later(lambda: expensive+expression()*to/calc)
And we don't see a lot of this happening. Why? I don't know for sure, but I can guess at a few possible reasons:
1) It's not part of the standard library, so you have to go fetch a thing to do it. If that's significant enough, this is solvable by adding it to the stdlib, or even a new builtin.
2) "later(lambda: expr)" is clunky. Very clunky. Your proposal solves that, by making "later expr" do that job, but at the price of creating some weird edge cases (for instance, you *cannot* parenthesize the expression - this is probably the only place where that's possible, as even non-expressions can often be parenthesized, eg import and with statements).
3) It's never actually the result of the expression, but always this proxy.
4) There's no (clean) way to get at the true object, which means that all the penalties are permanent.
5) Maybe the need just isn't that strong.
How much benefit would this be? You're proposing a syntactic construct for something that isn't used all that often, so it needs to be a fairly dramatic improvement in the cases where it _is_ used.