可迭代拆包的默认值

Default values for iterable unpacking

我经常因 Python 的可迭代拆包缺乏灵活性而感到沮丧。

举个例子:

a, b = range(2)

工作正常。正如预期的那样,a 包含 0b 包含 1。现在让我们试试这个:

a, b = range(1)

现在,我们得到 ValueError:

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

不理想,当期望的结果是 a 中的 0b 中的 None

有很多技巧可以解决这个问题。我见过的最优雅的是:

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

不漂亮,可能会让 Python 新手感到困惑。

那么最 Pythonic 的方法是什么?将 return 值存储在变量中并使用 if 块? *varname 黑客攻击?还有别的吗?

如评论中所述,最好的方法是让函数 return 具有恒定数量的值,如果您的用例实际上更复杂(如参数解析),请使用它的图书馆。

但是,您的问题明确要求使用 Pythonic 方法来处理 return 可变数量参数的函数,我相信它可以用 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 了解更多关于他们的信息。

下面是一个修饰函数,可以满足您的需求。函数 return 是一个具有可变数量参数的迭代器,它被填充到一定长度以更好地适应迭代器解包。

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)

输出您要查找的内容:

0 None None
0 1 None
0 1 2

装饰器基本上采用装饰函数和 return 其输出以及足够的额外值来填充 max_values。然后调用者可以假定该函数总是 return 恰好 max_values 个参数,并且可以像平常一样使用花式解包。

这是@supersam654 的装饰器解决方案的替代版本,使用迭代器而不是列表来提高效率:

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

用法相同:

@variable_return(3)
def ret_n(n):
    return tuple(range(n))

a, b, c = ret_n(2)

这也可以与非用户定义的函数一起使用,如下所示:

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