[Tutor] Defining variable arguments in a function in python

Avi Gross avigross at verizon.net
Sun Dec 30 11:07:19 EST 2018


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.net at python.org> On Behalf Of Steven D'Aprano
Sent: Sunday, December 30, 2018 5:39 AM
To: tutor at 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 at python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor



More information about the Tutor mailing list