One thing that’s worth remembering. These types of discussion are a lot of work for all participants, and take energy that would otherwise be directed to something else. That’s not a problem in itself, it’s just the nature of online discussions, but it does mean that any proposal needs to be at least reasonably likely to be successful, in order to justify the time spent on it.
A comment like yours suggests that you’re not that invested in moving the idea forward, so that by itself discourages people from supporting the idea. Better to support something that the proposer is actually enthusiastic about…
The OP proposes to change interfaces, like dict.get as follows:
class dict:
def get(self, key, default=None):
if key in self:
return self[key]
elif isinstance(default, lazy):
default = default()
return default
This will complicate the contract from “if key exists in the dict, then return it, otherwise return the default” to “if key exists in the dict, then return it, otherwise check if it is a lazy-object. If so, evaluate it and return the result. Otherwise, return the object.” This is much more complicated and gives a special treatment to some specific other class.
It would be much simpler (and much more orthogonal!), if dict.get would allow for a keyword argument “callback” where a callable can be provided that is called if the key is not found in the dict.
I don’t think this would be as convenient and scalable. I still think that (at least to address this specific case of “lazy arguments”), my proposed solution is the best that I have seen. Again, not saying that it is THE solution, just that it is the best so far and has been so for a long time to me.
However, this is a good idea in general. dict callbacks can be done in C. I have thought several times that it would be good to expose it to Python.
Thank you for being explicit, as this reveals an underlying source of disagreement. One person’s elegance is another’s ugliness. To many, Python elegance, and one of its design goals, is having multiple basic constructs that work together in an orthogonal fashion. Stuffing all computation into expressions is explicitly not one of its design goals. To me, this special-case gimmick, which would require special and explicit support in the few functions where it would fit, and special-case documentation that many users would miss, is a bit ugly.
It wasn’t in comparison to try-except, but to lazy_default: tuple = (func, args, kwds) argument.
In comparison to try-except my suggestion is IMO slightly less elegant, but more convenient + other advantages, such as being able to use it with defaultdict and scalability for any “lazy argument logic”.
Having that said, in general I agree. Orthogonal would be best. It just so happened that to satisfy all edge cases this is the best that I could find for this purpose. And I don’t really see the point of any effort towards doing work in this direction if solution is unable to cover large majority of cases. If it is half-functional, then doing nothing is likely better.
The next best, I think, is probably having lazy_default=True/False, which is more of an extension as opposed to object coupling. But this is much less convenient, especially for multiple lazy arguments. e.g.:
Although the most common case is Mapping.get, I think that if something new was introduced to address “lazy arguments”, it should be able to cover cases like:
def foo(a:int, b:int|lazy, c:int|lazy):
b = b() if type(b) is lazy else b
b = c() if type(c) is lazy else c
return a + b + c
In other words, if it is not some sort of generic solution/signalling method, then I don’t think I would find it satisfactory.
Thus, before there is a terminal halt on more general deferred evaluation and final conviction of it not being implemented, I don’t think this should be pushed forward.
Not sure what you are talking about. Python-ideas is a section of Python discussions where people propose ideas.
I had this need myself and have arrived at this solution. And thought that it might be useful for more general use.
Because this does not work for defaultdict, thus it is not a general solution for Mapping. There are other exceptions, such as ChainMap. General solution for this is:
if k in d:
result = d[k]
else:
result = default
But the whole point of this is to improve convenience for “general lazy arguments” including cases beyond “dict.get(default)”, thus even above does not address what this is aiming to improve, it only covers dict.get case.
Yes, and my point is that through the process of discussion, often you’ll find that the best solution is actually one that already exists. Inventing something new is NOT the only option here.
I have said this several times in this thread already.
I don’t think this should be said at all - everyone understands this I think. But I find it useful to do it so that people don’t get too stressed out. I have noticed that I receive less aggression when I keep reminding from time to time that I am not pushing mindlessly and am ok with outcome of just leaving things as they are.
I see some weak points in my idea, but decided to post it anyway:
let’s introduce a special type of lambdas called factory with exactly the same syntax and semantics as lambda except that it does not take arguments. Example:
general rule:
when a variable of this type (e.g. typing.Factory) appears in an expression, it gets automatically evaluated. Example:
return default # returns the result of expensive_func(key)
special case:
The automatic evaluation is suppressed when a factory is assigned to a variable annotated as Factory, possibly in Union with other types. This allows to pass a factory object and delay its evaluation until really necessary.
Short example:
def get_value(key:str, default=int|Factory[int]) -> int:
if key in storage:
return storage[key]
return default
That thread is a long, confusing mess with the first post, and honestly quite a lot of the first posts, being about an orthogonal syntax idea - the stuff you remember from it are only discussed pretty far down.
The thread above has explored this and has implementation which works for pure Python code.
The issue that it is stuck at is the difficulty of making it work in C extensions.
It would be lovely if that one is solved. It is interesting problem to nail down (even if it doesn’t make it to standard library). Have a read of 2nd half of the thread if you want to give us a hand there.
In contrast, the nature of this idea is how to find a simple alternative solution which avoids all the complexity and instead makes use of everyone agreeing on the pattern and default object to use.
It isn’t a big issue.
It can just as well be: Factory(lambda: <body>)
Soft keyword can be seen as an alias to it which can exist or not. Given this POV, in the mentioned thread this was postponed until the end.
In the mentioned thread the approach taken is: Factory a.k.a. defered object being devised so that it is a proxy similar to weakref — Weak references — Python 3.13.3 documentation with extra bits and pieces being handled by C.
Type hints is sort of new approach where this could be handled by parser instead of object level.
I am not sure what exactly you have in mind, but I suspect you think something along the lines of AST modification?
In this case, it does seem to suffer from the same problem - being limited to pure Python.
And C problem is even harder:
When evaluation is handled at object level, C code can introspect its type and act accordingly.
If this is handled by parser then I don’t see any way how this could be adapted by C code.
But we already have these. You think of it as some sort of unusual thing, while it could simply be viewed in the same light as any other slightly special object, such as None, NotImplemented, etc.
Missing documentation of this is not special in any way. And same as any new object, it would take time to adapt.
The positive of this is that it can be gradual. It doesn’t even need to be in builtins.
E.g. Can have a LazyType in types for a start. No need to even start using it for dict.get and just give some time to see if packages start adapting it.
If very few do, there is little harm done - just a small class object in types that some people use.
If it gets adopted more widely with time, then can adapt dict.get and similar methods in standard library to make use of it.
And can even alias it in builtins if there is a demand for it. But this would be fairly far down the line and uncertain.
First step would be just to make a small type and those who want can use it.
Implementation/maintenance cost is minimal - it is a dead simple object.
Worst case - there will be a type lying in types that isn’t used very much.
The best case, it gets adapted with time and user has 1 LazyType on which he can rely and which is accepted in standard library and 3rd party packages whenever appropriate.