User-friendly way to return multiple values from a function in Python
assert foo().bar in foobar().baz
Personally, I think that dictionary syntax for Python is the least well done thing in Python. All those name[‘in’][‘quotes’][‘and’][‘brakets’]
is really hard to read. It’s much better to have names.with.a.dot.notation
, isn’t it?
Unfortunately, dot notation is reserved for modules and classes for their moduly and classy things. A dictionary is just not ‘classy’ enough for dot notation. Bummer.
If you want to implement full-fledged class as a return value, you’ll find you need to support all those magic methods on comparison, calling, copying, hashing, etc. It’s a lot of boilerplate code. There are classes to make your life easier (nameddict, etc), but all them is still too much of a hassle for a simple case.
The case I’m talking about is much simpler: just make return more neat and easy to use.
The problem
We need to return a few values from a function.
Naive solutions
def foo():
return 1, 2, 'foo'
The key issue is that return values have zero meaning. To use them one need to unpack values into variables or access them by index:
assert foo()[2] == 'foo'
Indexing from zero adds fun to the count. Is password in an element number 5 or is it in an element number 3?
Another approach is to return a dictionary:
def foo():
return {
'odd': 1,
'even': 2,
'string_example': 'foo'
}
Now we got names for return values. In exchange caller need to suffer:
assert foo()['string_example'] == 'foo'
The good solution
My solution is to use class as a data holder. We’ll ignore all fancy things we can do, and keep code as simple as possible:
def foo():
class retval:
odd = 1
even = 2
string_example = 'foo'
return retval
The use is a bliss:
assert foo().string_example == 'foo'
But it’s become even better if you consider container types for return values:
def bar():
class retval:
somelist = []
adict = {}
retval.somelist.append(2)
retval.somelist.append(4)
retval.adict[3] = 4
retval.adict[’baz’] = 'something else'
return retval
In this example we don’t afraid of using class variables with container types, as the class ‘retval’ is created every time function is executed, so, no (un)expected surprises here.
It does not give you the full dot notation, but at least first one is for free:
assert bar().adict['baz'] == 'something else'
The reason I usually don’t want to have full dot notation is that those return values may be passed to another function, and that function is going to be surprised with non-standard object (or you need to write a lot of code to not to surprise).
assert 3 in bar().adict.keys()
If your adict
is not a dictionary, it’s either a surprise, or a lot of code. But one level of ‘classiness’ for multiple return values saves a lot of typing while preserving standard types.