[Python-ideas] kwargs for return

Steven D'Aprano steve at pearwood.info
Sun Jan 27 01:24:32 EST 2019


On Sat, Jan 26, 2019 at 10:20:11AM -0800, Christopher Barker wrote:

[...]
> Starting with the simplest example, when defining a function, you 
> can have one take a single positional parameter:
[...]
> Later on, if you want to exapand the API, ytou can add a keyword parameter:
> 
> def fun(x, y=None):
>     ...
> 
> And all the old code that already calls that function with one argument
> still works, and newer code can optionally specify the keyword argument --
> this is a really nice feature that makes Python very refactorable.

In the above example, the caller doesn't need to specify ``y`` as a 
keyword argument, they can call fun(obj) with a single positional 
argument too. Keyword arguments are a red-herring here.

What makes this work is not *keyword arguments* but default values. See 
below.

 
> But for return values, there is no such flexibility

With no default value, your keyword arguments MUST be supplied and 
backwards compatibility is broken. Given a default value, the called 
function merely sees the default value if no other value is given.

(You all know how this works, I trust I don't need to demonstrate.)

The symmetrically equivalent to arguments with defaults would be if we 
could supply defaults to the assignment targets, something like this 
syntax (for illustration purposes only):

spam, eggs, cheese="cheddar", aardvark=42 = func()

Now func() must return between two and four values. I trust the 
analogy with parameters with default values is obvious:

Calling a function:
- parameters without a default are mandatory;
- parameters with a default are optional;
- supplied arguments are bound to parameters from left to right;
- any parameters which don't get an argument have the default bound;
- if they don't have a default, it is an error.

Returning from a function (hypothetical):
- assignment targets without a default are mandatory;
- assignment targets with a default are optional;
- returned items are bound to targets from left to right;
- any target which don't get a result have the default bound;
- if they don't have a default, it is an error.

Note that all of this is based on positional arguments, so presumably it 
would use sequence unpacking and allow the equivalent of *args to 
collect additional positional arguments (if any):

spam, eggs, cheese="cheddar", aardvark=42, *extra = func()


Javascript already kind of works this way, because it has a default 
value of undefined, and destructuring assignment (sequence unpacking) 
assigns undefined to any variable that otherwise wouldn't get a value:

js> var a, b = 1
js> a === undefined
true
js> b
1


But none of this has anything to do with *keyword arguments*, let alone 
collecting kwargs as in the subject line.

The keyword argument analogy might suggest using some form of dict 
unpacking, but the complexity ramps up even higher:

1. At the callee's end, the function returns some sort of mapping 
between keys and items. For the sake of the argument, let's assume keys 
must be identifiers, and invent syntax to make it easier:

    def func():
        return spam=True, eggs=42, messages=[], cheese=(1, 2)

(syntax for illustration purposes only).

2. At the caller's end, we need to supply the key, the binding target 
(which may not be the same!), a possible default value, and somewhere to 
stash any unexpected key:value pairs. Let's say:

    spam, eggs->foo.bar, aardvark=None, **extra = func()

might bind:

    spam = True
    foo.bar = 42
    aardvark = None
    extra = {'messages': [], 'cheese': (1, 2)}


At this point somebody will say "Why can't we make the analogy between 
calling a function and returning from a function complete, and allow 
*both* positional arguments / sequence unpacking *and* keyword arguments 
/ dict unpacking at the same time?".


[...]
> Sure, if you had had the foresight, then you _could_ have written your
> original function to return a more flexible data structure (dict,
> NamedTuple, etc), but, well, we usually don't have that foresight :-).

*shrug*

That can apply to any part of the API. I now want to return an arbitrary 
float, but I documented that I only return positive ints... if only I 
had the foresight...


[...]
> So: IIUC, Thomas's idea is that there be some way to have"optional" return
> values, stabbing at a possible syntax to make the case:
[...]
> Now it can still be called as:
> 
> x = fun()
> 
> and result in x == 5
> 
> or:
> 
> x, y = fun()
> 
> and result in x == 5, y == 6

How do you distinguish between these three situations?

    # I don't care if you return other values, I only care about the first
    x = fun()  


    # Don't bother unpacking the result, just give it to me as a tuple
    x = fun()


    # Oops I forgot that fun() returns two values
    x = fun()



-- 
Steve


More information about the Python-ideas mailing list