[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