Are multiple return values really harmful? (Re: determining the number of output arguments)

Bengt Richter bokr at oz.net
Wed Nov 17 17:58:56 EST 2004


On Wed, 17 Nov 2004 16:49:03 +1300, Greg Ewing <greg at cosc.canterbury.ac.nz> wrote:

>Jeremy Bowers wrote:
>> Generally, returning a tuple is either a sign that your return value
>> should be wrapped up in a class, or the function is doing too much.
>
ISTM that a tuple _is_ a class that wraps content for return as a _single value_,
just like a custom container class instance. What's the problem? The fact that you
can write

    r,g,b = foo()

instead of

    t = foo()
    r = t[0]
    g = t[1]
    b = t[0]

(or some other verbose object-content-accessing code)
is very nice, if you ask me. There are lots of uses for
small ordered sets of values where no explicit naming
is required, any more than it is in a call to def foo(r,g,b): ...

I agree that unpacking long tuples to a series of local names
is bug-prone, just like a similar arg list for function call, but
it is efficient and sometimes that's worth the debugging of a
couple of lines of code. I don't think programmers should arbitrarily
be prevented from using tuples as they see fit.

Since the alternative of returning a dict of keyworded values is now
syntactically so simple (i.e., return dict(a=a_value, b=b_value, c=etc),
ISTM this is mostly a design/style issue, and I disagree with the idea
that returning small tuples is generally bad design.

>While I suspect you may be largely right, I
I suspect that most tuples returned are small, and the too-broad
criticism of tuple returning is therefore largely inappropriate ;-)

>find myself wondering why this should be so. We
>don't seem to have any trouble with multiple inputs
>to a function, so why should multiple outputs be
>a bad thing? What is the reason for this asymmetry?
Actually, it is not multiple outputs. It is a single tuple.
>
>Perhaps it has something to do with positional vs.
>keyword arguments. If a function has too many input
>arguments to remember what order they go in, we
>always have the option of specifying them by
>keyword. But we can't do that with the return-a-tuple-
>and-unpack technique for output arguments -- it's
>strictly positional.
We can (in current python) as easily return a dict as a tuple,
e.g., return dict(a=1, b=2, c=3) for your dict below.
>
>Maybe things would be better if we had "dict unpacking":
>
>   a, c, b = {'a': 1, 'b': 2, 'c': 3}

Yes, that is an interesting idea. It's part of the larget issue
of programmatic creation of bindings in the local namespace. The
trouble is that locals() effectively generates a snapshot dict
of current local bindings, and it's possible to mutate this dict,
but it's not possible (w/o black magic) to get the mods back into
the actual local bindings. If locals() returned a proxy for the
local namespace that could rebind local names, we could write

    localsproxy().update({'a': 1, 'b': 2, 'c': 3})

instead of your line above. Likewise if foo returned a dict,

    localsproxy().update(foo())

Maybe keyword unpacking could spell that with a '**' assignment target,
e.g.,

    ** = foo()  # update local bindings with all legal-name bindings in returned dict

Hm, maybe you could use the same sugar for attributes, e.g.,

    obj.** = foo()  # sugar for obj.__dict__.update(foo())

IWT it would be acceptable to limit binding to existing bindings, so that frame structure
would not have to be altered. Still, as Carlos pointed out, formal parameter names
are private to a function, and their raison d'etre is to decouple function code internal
naming from external naming. Returning named values (whether using dict per se or the
attribute dict of a custom object, etc) creates coupling of a kind again. Or course, so
does any custom object with named attributes or methods.
>
>would give a == 1, c == 3, b == 2. Then we could
>accept outputs by keyword as well as inputs...
>
I think 

    a, c, b = {'a': 1, 'b': 2, 'c': 3}

probably needs an unpacking indicator in the syntax, e.g.,

    a, c, b =  **{'a': 1, 'b': 2, 'c': 3}

And I think this should be sugar for the effect

    a       =    {'a': 1, 'b': 2, 'c': 3}['a']
    c       =    {'a': 1, 'b': 2, 'c': 3}['c']
    b       =    {'a': 1, 'b': 2, 'c': 3}['b']

I.e., if the dict has additional content, it is not an error
Hm, do you want to go for a dict.get-like default value in that?

    a, c, b =  **(default_value){'a': 1, 'b': 2}

would be sugar for

    a       =    {'a': 1, 'b': 2, 'c': 3}.get('a', default_value)
    c       =    {'a': 1, 'b': 2, 'c': 3}.get('c', default_value)
    b       =    {'a': 1, 'b': 2, 'c': 3}.get('b', default_value)

Hm, better stop ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list