Steve,

I had the same thoughts and many more when I played with these ideas last 
night. I thought I stated clearly that this was an EXPLORATION and not a 
serious solution. So if that is accepted, we can certainly discuss merits 
without giving demerits as well as the many flaws and dangers lurking in any 
such design.


To begin with, anyone trying any variants of a design like this needs to be 
sure it is well documented. I have seen functions that do something vaguely 
similar and often with unexpected results.

Consider a NORMAL function with no external gimmicks. Say it accepts an 
argument (positional or by name does not matter) and processes it by checking 
they argument type, the specific values against a range expected, and so on. It 
then unilaterally makes changes. If it expects a single value of type integer, 
ang gets a floating point number, it may round or truncate it back to an 
integer. If it gets a complex number, it drops the imaginary part. If it gets a 
list or tuple or iterator of any sort, it just takes the first entry and 
coerces it into an integer. If it sees a matrix or other challenging object, it 
flattens it then ... If it sees a character string, it tries to convert 
something like "one" to an integer. If your choice is in some sense wrong, or 
just not convenient, it replaces it with another choice. For example, it may 
move your integer to the nearest prime integer. It may reject your suggestion 
to make a window on your screen a trillion pixels wide and drop it to 1024. 

Sometimes it prints a warning about changes it has made. Sometimes it won't run 
but print out a message telling you what value it might have accepted.

Again, discussing a different scenario. Would you agree that kind of design 
does happen and sometimes is seen as a smart thing but also as full of pitfalls 
even sometimes for the wary?

In that spirit, any design like the ones I played with is equally scary. Even 
worse, I have a QUESTION. Let me remind readers of the initial idea behind 
this. You have a function in which you want to communicate with the python 
ruler that it have a positional argument that will be switched to a default. 
But the current rules are that the only way to make something optional is by 
making it a keyword parameter and declare the default right there. If the 
function template is:

def hello(a=5)

then you can do any of the following with reasonable results:

hello()
hello(7)
hello(a=7)

The default is only applied in the first version.

The problem is if you want a non-key word to be used as a default and then also 
want to gather up additional positional arguments and so on. There is no way 
designed to do that and possibly there should not be.

So what I am trying to understand is this. When someone types in a function 
invocation and it is evaluated, when does the programmer get a chance to 
intervene? Something in what I will call the interpreter seems to look at what 
the programmer typed just now and sees:
hello()
hello(1)
hello(1,2)

and so on. It looks up the object that encapsulates the function hello is 
pointing to. It examines the many parts hidden within and determines what the 
designer asked for. If it sees that the actual call does not match, it geerally 
produces an error message like this:

>>> def hello(n): print(n)

>>> hello()
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    hello()
TypeError: hello() missing 1 required positional argument: 'n'

That is a reasonable thing to do. But in my idiotic design, I would like the 
user to be told a tad more. I want them told that if they want the default, use 
an n consisting of an ellipsis as in
hello(...)

to get the default.

Is there a way to create a function and set it up so you have some control over 
the error message? Without that, this kind of function is even more dangerous.

This message is long enough. I will reply to Steven's specific points in a bit. 
Still on vacation 😊


-----Original Message-----
From: Tutor <tutor-bounces+avigross=verizon....@python.org> On Behalf Of Steven 
D'Aprano
Sent: Sunday, December 30, 2018 5:39 AM
To: tutor@python.org
Subject: Re: [Tutor] Defining variable arguments in a function in python

On Sun, Dec 30, 2018 at 12:07:20AM -0500, Avi Gross wrote:

[...]
> Or on a more practical level, say a function wants an input from 1 to 10.
> The if statement above can be something like:
> 
> >>> def hello(a, *n, **m) :
>       if not (1 <= a <= 10) : a=5
>       print(a)
>       print(*n)
> 
>       
> >>> hello(1,2,3)
> 1
> 2 3
> >>> hello(21,2,3)
> 5
> 2 3
> >>> hello(-5,2,3)
> 5
> 2 3


This design is an example of "an attractive nuisance", possibly even a "bug 
magnet". At first glance, when used for mickey-mouse toy examples like this, it 
seems quite reasonable:

    hello(999, 1, 2)  # I want the default value instead of 999

but thinking about it a bit more deeply, and you will recognise some problems 
with it.

First problem: 

How do you know what value to pass if you want the default? Is 999 out of 
range? How about 11? 10? Who knows? If you have to look up the docs to know 
what counts as out of range, you might as well read the docs to find out what 
the default it, and just pass that:

    hello(5, 1, 2)  # I want the default value 5

but that kind of defeats the purpose of a default. The whole point of a default 
is that you shouldn't need to pass *anything at all*, not even a placeholder.

(If you need a placeholder, then you probably need to change your function 
parameters.)

But at least with sentinels like None, or Ellipsis, it is *obvious* that the 
value is probably a placeholder. With a placeholder like 11 or 999, it isn't. 
They look like ordinary values.


Second problem:

Most of the time, we don't pass literal values to toy functions. We do 
something like this example:

    for number, widget_ID, delivery_date in active_orders:
        submit_order(number, widget_ID, delivery_date)

Can you see the bug? Of course you can't. There's no obvious bug. But little do 
you know, one of the orders was accidentally entered with an out-of-range 
value, let's say -1, and instead of getting an nice error message telling you 
that there's a problem that you need to fix, the
submit_order() function silently replaces the erroneous value with the default.

The bug here is that submit_order() function exceeds its authority.

The name tells us that it submits orders, but it also silently decides to 
change invalid orders to valid orders using some default value. But this fact 
isn't obvious from either the name or the code. You only learn this fact by 
digging into the source code, or reading the documentation, and let's be 
honest, nobody wants to do either of those unless you really have to.

So when faced with an invalid order, instead of getting an error that you can 
fix, or even silently skipping the bad order, the submit_order() function 
silently changes it to a valid-looking but WRONG order that you probably didn't 
want. And that costs real money.

The risk of this sort of bug comes directly from the design of the function. 
While I suppose I must acknowledge that (hypothetically) there could be 
use-cases for this sort of design, I maintain that in general this design is a 
bug magnet: responsibility for changing out-of-range values to in-range values 
belongs with the caller, not the called function.

The caller may delegate that responsibility to another:

    for number, widget_ID, delivery_date in active_orders:
        number = validate_or_replace(number)
        submit_order(number, widget_ID, delivery_date)

which is fine because it is explicit and right there in plain sight.

This then allows us to make the submit_order() far more resiliant: if it is 
passed an invalid order, it can either fail fast, giving an obvious error, or 
at least skip the invalid order and notify the responsible people.


--
Steven
_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

Reply via email to