Iterative Unpacking Defaults

I am often disappointed with the lack of flexibility in unpacking the Python iteration.

Take the following example:

a, b = range(2) 

It works great. a contains 0 and b contains 1 , as expected. Now try this:

 a, b = range(1) 

Now we get a ValueError :

 ValueError: not enough values to unpack (expected 2, got 1) 

Not ideal when the desired result was 0 in a and None in b .

There are several hacks to get around this. The most elegant I've seen is:

 a, *b = function_with_variable_number_of_return_values() b = b[0] if b else None 

Not really, and can be confusing for Python newbies.

So what is the most pythonic way to do this? Store the return value in a variable and use the if block? *varname hack? Something else?

+5
source share
2 answers

As mentioned in the comments, the best way to do this is to simply return your function to a constant number of values, and if your use case is actually more complex (e.g. parsing arguments), use a library for it.

However, in your question, I explicitly asked you to use the Pythonic method to handle functions that return a variable number of arguments, and I believe that this can be done using decorators . They are not very common, and most people tend to use them more than creating them, so here's a tutorial on creating decorators. Learn more about them.

Below is a decorated function that does what you are looking for. The function returns an iterator with a variable number of arguments and is filled to a certain length in order to better accommodate the unpacking of the iterator.

 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 are looking for:

 0 None None 0 1 None 0 1 2 

The decorator basically takes a decorated function and returns its result, along with sufficient additional values ​​to fill max_values . Then the caller can assume that the function always returns exactly the max_values number of arguments and can use fancy decompression, as usual.

+1
source

Here's an alternative version of the by @ supersam654 decorator solution, using iterators rather than lists for efficiency:

 def variable_return(max_values, default=None): def decorator(f): def wrapper(*args, **kwargs): actual_values = f(*args, **kwargs) try: for count, value in enumerate(actual_values, 1): yield value except TypeError: count = 1 yield actual_values yield from [default] * (max_values - count) return wrapper return decorator 

It is used in the same way:

 @variable_return(3) def ret_n(n): return tuple(range(n)) a, b, c = ret_n(2) 

It can also be used with non-user-defined functions:

 a, b, c = variable_return(3)(range)(2) 
0
source

Source: https://habr.com/ru/post/1268957/


All Articles