On 25.10.2021 14:26, Chris Angelico wrote:
> On Mon, Oct 25, 2021 at 11:20 PM Marc-Andre Lemburg <[email protected]> wrote:
>>
>> On 25.10.2021 13:53, Chris Angelico wrote:
>>> On Mon, Oct 25, 2021 at 10:39 PM Marc-Andre Lemburg <[email protected]> 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/

_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/[email protected]/message/BZXRU23Y7KDFTG676OMAZS2DMZPABUBI/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to