Default values for iterable unpacking

后端 未结 2 687
面向向阳花
面向向阳花 2021-01-18 02:28

I\'ve often been frustrated by the lack of flexibility in Python\'s iterable unpacking.

Take the following example:

a, b = range(2)

相关标签:
2条回答
  • 2021-01-18 02:36

    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.

    0 讨论(0)
  • 2021-01-18 02:59

    Here's an alternative version of the decorator solution by @supersam654, 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's used in the same way:

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

    This could also be used with non-user-defined functions like so:

    a, b, c = variable_return(3)(range)(2)
    
    0 讨论(0)
提交回复
热议问题