Well, you could do...
first,second = foo(True) or (None,None)
first,second = foo(False) or (None,None)
but as far as I know there's no simpler way to expand None to fill in the entirety of a tuple.
Answer from Amber on Stack OverflowWell, you could do...
first,second = foo(True) or (None,None)
first,second = foo(False) or (None,None)
but as far as I know there's no simpler way to expand None to fill in the entirety of a tuple.
I don't see what is wrong with returning (None,None). It is much cleaner than the solutions suggested here which involve far more changes in your code.
It also doesn't make sense that you want None to automagically be split into 2 variables.
How about raise an ArgumentError? Then you could try calling it, and deal with the exception if the argument is wrong.
So, something like:
try:
v1, v2, v3 = MyFunc()
except ArgumentError:
#deal with it
Also, see katrielalex's answer for using a subclass of ArgumentError.
This should work nicely:
v1, v2, v3 = MyFunc() or (None, None, None)
When MyFunc() returns a tuple, it will be unpacked, otherwise it will be substituted for a 3-tuple of None.
This is function that uses beautiful soup to find the <a> tag which contains the URI segment that needs to be appended onto the base URI.
def get_next_page(soup, page):
next = page+1
anchors = soup.find_all('a', {'href':True})
for each in anchors:
suffix = each['href'].strip()
l = len(suffix)
try:
next_check = int(suffix[l:l+1])
except ValueError:
next_check = 0
if next_check == next:
return (suffix, next_check)I call it with this: next_page, page = get_next_page(soup, page)
Initially I was having problems trying to determine if the href value I was looking for (after being sliced) was equal to the counter-page number variable (which is just page ). To remedy this I used try: x = int(y) Except ValueError: x= 0.
As you can see through, when that test is successful, it returns the full href value (which has to be something because I use {'href':True} in my .find_all parameters. The other part of the tuple being returned is next_check ...which equals next... and next is just a counter that should be a value of 2 when this function runs for the first time. [In another function I start with page = 1 then I call the function above with page. page is incremented and assigned to a different variable (next), and ultimately returned... so that too should have a value. In other words, I cannot see where either one of the tuple values being returned is None.
I think the choices need to be considered strictly from the caller's point of view: what is the consumer most likely to need to do?
And what are the salient features of each collection?
- The tuple is accessed in order and immutable
- The list is accessed in order and mutable
- The dict is accessed by key
The list and tuple are equivalent for access, but the list is mutable. Well, that doesn't matter to me the caller if I'm going to immediately unpack the results:
score, top_player = play_round(players)
# or
idx, record = find_longest(records)
There's no reason here for me to care if it's a list or a tuple, and the tuple is simpler on both sides.
On the other hand, if the returned collection is going to be kept whole and used as a collection:
points = calculate_vertices(shape)
points.append(another_point)
# Make a new shape
then it might make sense for the return to be mutable. Homogeneity is also an important factor here. Say you've written a function to search a sequence for repeated patterns. The information I get back is the index in the sequence of the first instance of the pattern, the number of repeats, and the pattern itself. Those aren't the same kinds of thing. Even though I might keep the pieces together, there's no reason that I would want to mutate the collection. This is not a list.
Now for the dictionary.
the last one creates more readable code because you have named outputs
Yes, having keys for the fields makes heterogenous data more explicit, but it also comes with some encumbrance. Again, for the case of "I'm just going to unpack the stuff", this
round_results = play_round(players)
score, top_player = round_results["score"], round_results["top_player"]
(even if you avoid literal strings for the keys), is unnecessary busywork compared to the tuple version.
The question here is threefold: how complex is the collection, how long is the collection going to be kept together, and are we going to need to use this same kind of collection in a bunch of different places?
I'd suggest that a keyed-access return value starts making more sense than a tuple when there are more than about three members, and especially where there is nesting:
shape["transform"]["raw_matrix"][0, 1]
# vs.
shape[2][4][0, 1]
That leads into the next question: is the collection going to leave this scope intact, somewhere away from the call that created it? Keyed access over there will absolutely help understandability.
The third question -- reuse -- points to a simple custom datatype as a fourth option that you didn't present.
Is the structure solely owned by this one function? Or are you creating the same dictionary layout in many places? Do many other parts of the program need to operate on this structure? A repeated dictionary layout should be factored out to a class. The bonus there is that you can attach behavior: maybe some of the functions operating on the data get encapsulated as methods.
A fifth good, lightweight, option is namedtuple(). This is in essence the immutable form of the dictionary return value.
Don't think about functions returning multiple arguments. Conceptually, it is best to think of functions as both receiving and returning a single argument. A function that appears to accept multiple arguments actually receives just a single argument of tuple (formally product) type. Similarly, a function that returns multiple arguments is simply returning a tuple.
In Python:
def func(a, b, c):
return b, c
could be rewritten as
def func(my_triple):
return (my_triple[1], my_triple[2])
to make the comparison obvious.
The first case is merely syntactic sugar for the latter; both receive a triple as an argument, but the first pattern-matches on its argument to perform automatic destructuring into its constituent components. Thus, even languages without full-on general pattern-matching admit some form of basic pattern matching on some of their types (Python admits pattern-matching on both product and record types).
To return to the question at hand: there is no single answer to your question, because it would be like asking "what should be the return type of an arbitrary function"? It depends on the function and the use case. And, incidentally, if the "multiple return values" are really independent, then they should probably be computed by separate functions.
EAFP:
try:
x,y = nontest()
except TypeError:
# do the None-thing here or pass
or without try-except:
res = nontest()
if res is None:
....
else:
x, y = res
How about:
x,y = nontest() or (None,None)
If nontest returns a two-item tuple like it should, then x and y are assigned to the items in the tuple. Otherwise, x and y are each assigned to none. Downside to this is that you can't run special code if nontest comes back empty (the above answers can help you if that is your goal). Upside is that it is clean and easy to read/maintain.
The pythonic way would be not to care about the type at all. Return a tuple, and if the calling function needs a list, then let it call list() on the result. Or vice versa, whichever makes more sense as a default type.
Even better, have it return a generator expression:
def names(self, section):
return (m[0] for m in self.items(section))
Now the caller gets an iterable that is evaluated lazily. He then can decide to iterate over it:
for name in obj.names(section):
...
or create a list or tuple from it from scratch - he never has to change an existing list into a tuple or vice versa, so this is efficient in all cases:
mylist = list(obj.names(section))
mytuple = tuple(obj.names(section))
Keep it simple:
def names(self, section):
"""Returns a list of names."""
return [m[0] for m in self.items(section)]
If the caller wants a tuple instead of a list, he does this:
names = tuple(obj.names(section))
If you know the function is always going to return a one-element tuple, you can use a one-element tuple assignment:
output, = fun(other_func())
or simply index:
output = fun(other_func())[0]
But in this case, a simple Don't do that, don't return a tuple might also apply:
output = other_func()
As long as *args is a tuple, returning args will therefore return a tuple, even if there is only one parameter.
You should probably do something like:
def fun(*args):
if len(args) == 1:
args, = args
return args