As mentioned in the comments, the best way to do this is to simply have your function return a constant number of values and if your use case is actually more complicated (like argument parsing), use a library for it.
However, your question explicitly asked for a Pythonic way of handling functions that return a variable number of arguments and I believe it can be cleanly accomplished with decorators. They're not super common and most people tend to use them more than create them so here's a down-to-earth tutorial on creating decorators to learn more about them.
Below is a decorated function that does what you're looking for. The function returns an iterator with a variable number of arguments and it is padded up to a certain length to better accommodate iterator unpacking.
def variable_return(max_values, default=None):
# This decorator is somewhat more complicated because the decorator
# itself needs to take arguments.
def decorator(f):
def wrapper(*args, **kwargs):
actual_values = f(*args, **kwargs)
try:
# This will fail if `actual_values` is a single value.
# Such as a single integer or just `None`.
actual_values = list(actual_values)
except:
actual_values = [actual_values]
extra = [default] * (max_values - len(actual_values))
actual_values.extend(extra)
return actual_values
return wrapper
return decorator
@variable_return(max_values=3)
# This would be a function that actually does something.
# It should not return more values than `max_values`.
def ret_n(n):
return list(range(n))
a, b, c = ret_n(1)
print(a, b, c)
a, b, c = ret_n(2)
print(a, b, c)
a, b, c = ret_n(3)
print(a, b, c)
Which outputs what you're looking for:
0 None None
0 1 None
0 1 2
The decorator basically takes the decorated function and returns its output along with enough extra values to fill in max_values
. The caller can then assume that the function always returns exactly max_values
number of arguments and can use fancy unpacking like normal.
a, b, *rest = function_returning_list()
– Carlynneb = b[0] if b else None
potentially ignores the third and above elements, quietly ignoring an unexpected occurrence, which is absolutely not Pythonic. And there should be an error in that case if you need two things but it only gives one. – Animator_, arg1, arg2, *rest = sys.argv
would sometimes work, but would often raise an error. – Carlynneargparse
, which will also generate useful help for you, or something likeclick
. – Animatorb, rest = (b[0], b[1:]) if b else (None, [])
– Carlynneargparse
would be better for parsing arguments, and it really does depend on the situation otherwise. You could useiter()
andnext()
with a default, or a wrapper around that, or coroutines, or a function that fills in up to some number of values with defaults, or… – Ewen