
On 25.10.2021 14:26, Chris Angelico wrote:
On Mon, Oct 25, 2021 at 11:20 PM Marc-Andre Lemburg <mal@egenix.com> wrote:
On 25.10.2021 13:53, Chris Angelico wrote:
On Mon, Oct 25, 2021 at 10:39 PM Marc-Andre Lemburg <mal@egenix.com> wrote:
I would prefer to not go down this path.
"Explicit is better than implicit" and this is too much "implicit" for my taste :-)
For simple use cases, this may save a few lines of code, but as soon as you end up having to think whether the expression will evaluate to the right value at function call time, the scope it gets executed in, what to do with exceptions, etc., you're introducing too much confusion with this syntax.
It's always possible to be more "explicit", as long as explicit means "telling the computer precisely what to do". But Python has default arguments for a reason. Instead of simply allowing arguments to be optional, and then ALWAYS having code inside the function to provide values when they are omitted, Python allows us to provide actual default values that are visible to the caller (eg in help()). This is a good thing. Is it "implicit"? Yes, in a sense. But it's very clear what happens if the argument is omitted. The exact same thing is true with these defaults; you can see what happens.
The only difference is whether it is a *value* or an *expression* that defines the default. Either way, if the argument is omitted, the given default is used instead.
I guess I wasn't clear enough. What I mean with "implicit" is that execution of the expression is delayed by simply adding a ">" to the keyword default parameter definition.
Given that this alters the timing of evaluation, a single character does not create enough attention to make this choice explicit.
If I instead write:
def process_files(processor, files=deferred(os.listdir(DEFAULT_DIR))):
def process_files(processor, files=deferred("os.listdir(DEFAULT_DIR)")):
it is pretty clear that something is happening at a different time than function definition time :-)
Even better: the deferred() object can be passed in as a value and does not have to be defined when defining the function, since the function will obviously know what to do with such deferred() objects.
Actually, I consider that to be far far worse, since it looks like deferred() is a callable that takes the *result* of calling os.listdir. Maybe it would be different if it were deferred("os.listdir(DEFAULT_DIR)"), but now we're losing a lot of clarity.
Yes, sorry. I forgot to add the quotes. The idea is to take the argument and essentially prepend the parameter processing to the function call logic, or even build a new function with the code added at the top.
If it's done with syntax, it can have special behaviour. If it looks like a function call (or class constructor), it doesn't look like it has special behaviour.
Having the explicit code at the start of the function is more flexible and does not introduce such questions.
Then use the explicit code! For this situation, it seems perfectly reasonable to write it either way.
But for plenty of other examples, it makes a lot of sense to late-bind in a more visible way. It's for those situations that the feature would exist.
Sure, you can always find examples where late binding may make sense and it's still possible to write explicit code for this as well, but that's not the point.
By introducing new syntax, you always increase the potential for readers not knowing about the new syntax, misunderstanding what the syntax means, or even not paying attention to the subtleties it introduces.
So whenever new syntax is discussed, I think it's important to look at it from the perspective of a user who hasn't seen it before (could be a programmer new to Python or one who has not worked with the new feature before).
I actually have a plan for that exact perspective. Was going to arrange things tonight, but it may have to wait for later in the week.
Ok :-)
In this particular case, I find the syntax not ideal in making it clear that evaluation is deferred. It's also not intuitive where exactly execution will happen (before entering the function, in which order, in a separate scope, etc).
Why not turn this into a decorator instead ?
@deferred(files=os.listdir(DEFAULT_DIR))
@deferred(files="os.listdir(DEFAULT_DIR)")
def process_files(processor, files=None):
Same reason. This most definitely looks like it has to calculate the directory listing in advance. It also is extremely difficult to explain how this is able to refer to other parameters, such as in the bisect example.
Not really, since the code you provide will simply get inlined and then does have full access to the parameter names and their values.
It's also extremely verbose, given that it's making a very small difference to the behaviour - all it changes is when something is calculated (and, for technical reasons, where; but I expect that intuition will cover that).
It is verbose indeed, which is why I still think that putting such code directly at the top of the function is the better way to go :-)
That's why I want a syntax that keeps things close to the function header, and keeps it looking like an argument default. When someone looks at the syntax for a 'def' statement, the two ways of doing argument defaults should look more similar than, say, early-bound-default and parameter-annotation. Currently, those two are very similar (just a change of punctuation), but late-bound defaults have to be done in a very different way, either in a decorator or in the function body.
That's what I want to improve.
That's fair, but since the late binding code will have to sit at the top of the function definition anyway, you're not really saving much. def add_item(item, target=>[]): vs. def add_item(item, target=None): if target is None: target = [] -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, Oct 25 2021)
Python Projects, Coaching and Support ... https://www.egenix.com/ Python Product Development ... https://consulting.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 https://www.egenix.com/company/contact/ https://www.malemburg.com/